From b80315df5dbf4531aef53c09ec072bfb9b9ad178 Mon Sep 17 00:00:00 2001 From: Aman Varshney Date: Wed, 22 Apr 2026 17:50:36 +0530 Subject: [PATCH 1/4] chore: bump oxlint/oxfmt and ignore public folder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - oxfmt ^0.33.0 → ^0.46.0 - oxlint ^1.48.0 → ^1.61.0 - add **/public/** to ignorePatterns in .oxfmtrc.json and .oxlintrc.json --- .oxfmtrc.json | 2 +- .oxlintrc.json | 2 +- pnpm-lock.yaml | 330 ++++++++++++++++++++++---------------------- pnpm-workspace.yaml | 4 +- 4 files changed, 169 insertions(+), 169 deletions(-) diff --git a/.oxfmtrc.json b/.oxfmtrc.json index 00eafc1c1b..24c4e69799 100644 --- a/.oxfmtrc.json +++ b/.oxfmtrc.json @@ -1,4 +1,4 @@ { "$schema": "./node_modules/oxfmt/configuration_schema.json", - "ignorePatterns": ["**/dist/**", "**/.next/**", "**/.source/**"] + "ignorePatterns": ["**/dist/**", "**/.next/**", "**/.source/**", "**/public/**"] } diff --git a/.oxlintrc.json b/.oxlintrc.json index c6225a55df..a5cea25458 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -140,5 +140,5 @@ "builtin": true }, "globals": {}, - "ignorePatterns": ["**/dist/**", "**/.next/**", "**/.source/**", "**/node_modules/**"] + "ignorePatterns": ["**/dist/**", "**/.next/**", "**/.source/**", "**/node_modules/**", "**/public/**"] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 64b5b7b111..dd8687a0f0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -145,11 +145,11 @@ catalogs: specifier: ^3.0.1 version: 3.0.1 oxfmt: - specifier: ^0.33.0 - version: 0.33.0 + specifier: ^0.46.0 + version: 0.46.0 oxlint: - specifier: ^1.48.0 - version: 1.58.0 + specifier: ^1.61.0 + version: 1.61.0 postcss: specifier: ^8.5.6 version: 8.5.8 @@ -227,10 +227,10 @@ importers: version: 25.5.0 oxfmt: specifier: 'catalog:' - version: 0.33.0 + version: 0.46.0 oxlint: specifier: 'catalog:' - version: 1.58.0 + version: 1.61.0 turbo: specifier: 'catalog:' version: 2.9.3 @@ -2284,246 +2284,246 @@ packages: cpu: [x64] os: [win32] - '@oxfmt/binding-android-arm-eabi@0.33.0': - resolution: {integrity: sha512-ML6qRW8/HiBANteqfyFAR1Zu0VrJu+6o4gkPLsssq74hQ7wDMkufBYJXI16PGSERxEYNwKxO5fesCuMssgTv9w==} + '@oxfmt/binding-android-arm-eabi@0.46.0': + resolution: {integrity: sha512-b1doV4WRcJU+BESSlCvCjV+5CEr/T6h0frArAdV26Nir+gGNFNaylvDiiMPfF1pxeV0txZEs38ojzJaxBYg+ng==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxfmt/binding-android-arm64@0.33.0': - resolution: {integrity: sha512-WimmcyrGpTOntj7F7CO9RMssncOKYall93nBnzJbI2ZZDhVRuCkvFwTpwz80cZqwYm5udXRXfF40ZXcCxjp9jg==} + '@oxfmt/binding-android-arm64@0.46.0': + resolution: {integrity: sha512-v6+HhjsoV3GO0u2u9jLSAZrvWfTraDxKofUIQ7/ktS7tzS+epVsxdHmeM+XxuNcAY/nWxxU1Sg4JcGTNRXraBA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxfmt/binding-darwin-arm64@0.33.0': - resolution: {integrity: sha512-PorspsX9O5ISstVaq34OK4esN0LVcuU4DVg+XuSqJsfJ//gn6z6WH2Tt7s0rTQaqEcp76g7+QdWQOmnJDZsEVg==} + '@oxfmt/binding-darwin-arm64@0.46.0': + resolution: {integrity: sha512-3eeooJGrqGIlI5MyryDZsAcKXSmKIgAD4yYtfRrRJzXZ0UTFZtiSveIur56YPrGMYZwT4XyVhHsMqrNwr1XeFA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxfmt/binding-darwin-x64@0.33.0': - resolution: {integrity: sha512-8278bqQtOcHRPhhzcqwN9KIideut+cftBjF8d2TOsSQrlsJSFx41wCCJ38mFmH9NOmU1M+x9jpeobHnbRP1okw==} + '@oxfmt/binding-darwin-x64@0.46.0': + resolution: {integrity: sha512-QG8BDM0CXWbu84k2SKmCqfEddPQPFiBicwtYnLqHRWZZl57HbtOLRMac/KTq2NO4AEc4ICCBpFxJIV9zcqYfkQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxfmt/binding-freebsd-x64@0.33.0': - resolution: {integrity: sha512-BiqYVwWFHLf5dkfg0aCKsXa9rpi//vH1+xePCpd7Ulz9yp9pJKP4DWgS5g+OW8MaqOtt7iyAszhxtk/j1nDKHQ==} + '@oxfmt/binding-freebsd-x64@0.46.0': + resolution: {integrity: sha512-9DdCqS/n2ncu/Chazvt3cpgAjAmIGQDz7hFKSrNItMApyV/Ja9mz3hD4JakIE3nS8PW9smEbPWnb389QLBY4nw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxfmt/binding-linux-arm-gnueabihf@0.33.0': - resolution: {integrity: sha512-oAVmmurXx0OKbNOVv71oK92LsF1LwYWpnhDnX0VaAy/NLsCKf4B7Zo7lxkJh80nfhU20TibcdwYfoHVaqlStPQ==} + '@oxfmt/binding-linux-arm-gnueabihf@0.46.0': + resolution: {integrity: sha512-Dgs7VeE2jT0LHMhw6tPEt0xQYe54kBqHEovmWsv4FVQlegCOvlIJNx0S8n4vj8WUtpT+Z6BD2HhKJPLglLxvZg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm-musleabihf@0.33.0': - resolution: {integrity: sha512-YB6S8CiRol59oRxnuclJiWoV6l+l8ru/NsuQNYjXZnnPXfSTXKtMLWHCnL/figpCFYA1E7JyjrBbar1qxe2aZg==} + '@oxfmt/binding-linux-arm-musleabihf@0.46.0': + resolution: {integrity: sha512-Zxn3adhTH13JKnU4xXJj8FeEfF680XjXh3gSShKl57HCMBRde2tUJTgogV/1MSHA80PJEVrDa7r66TLVq3Ia7Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm64-gnu@0.33.0': - resolution: {integrity: sha512-hrYy+FpWoB6N24E9oGRimhVkqlls9yeqcRmQakEPUHoAbij6rYxsHHYIp3+FHRiQZFAOUxWKn/CCQoy/Mv3Dgw==} + '@oxfmt/binding-linux-arm64-gnu@0.46.0': + resolution: {integrity: sha512-+TWipjrgVM8D7aIdDD0tlr3teLTTvQTn7QTE5BpT10H1Fj82gfdn9X6nn2sDgx/MepuSCfSnzFNJq2paLL0OiA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-arm64-musl@0.33.0': - resolution: {integrity: sha512-O1YIzymGRdWj9cG5iVTjkP7zk9/hSaVN8ZEbqMnWZjLC1phXlv54cUvANGGXndgJp2JS4W9XENn7eo5I4jZueg==} + '@oxfmt/binding-linux-arm64-musl@0.46.0': + resolution: {integrity: sha512-aAUPBWJ1lGwwnxZUEDLJ94+Iy6MuwJwPxUgO4sCA5mEEyDk7b+cDQ+JpX1VR150Zoyd+D49gsrUzpUK5h587Eg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxfmt/binding-linux-ppc64-gnu@0.33.0': - resolution: {integrity: sha512-2lrkNe+B0w1tCgQTaozfUNQCYMbqKKCGcnTDATmWCZzO77W2sh+3n04r1lk9Q1CK3bI+C3fPwhFPUR2X2BvlyQ==} + '@oxfmt/binding-linux-ppc64-gnu@0.46.0': + resolution: {integrity: sha512-ufBCJukyFX/UDrokP/r6BGDoTInnsDs7bxyzKAgMiZlt2Qu8GPJSJ6Zm6whIiJzKk0naxA8ilwmbO1LMw6Htxw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-riscv64-gnu@0.33.0': - resolution: {integrity: sha512-8DSG1q0M6097vowHAkEyHnKed75/BWr1IBtgCJfytnWQg+Jn1X4DryhfjqonKZOZiv74oFQl5J8TCbdDuXXdtQ==} + '@oxfmt/binding-linux-riscv64-gnu@0.46.0': + resolution: {integrity: sha512-eqtlC2YmPqjun76R1gVfGLuKWx7NuEnLEAudZ7n6ipSKbCZTqIKSs1b5Y8K/JHZsRpLkeSmAAjig5HOIg8fQzQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-riscv64-musl@0.33.0': - resolution: {integrity: sha512-eWaxnpPz7+p0QGUnw7GGviVBDOXabr6Cd0w7S/vnWTqQo9z1VroT7XXFnJEZ3dBwxMB9lphyuuYi/GLTCxqxlg==} + '@oxfmt/binding-linux-riscv64-musl@0.46.0': + resolution: {integrity: sha512-yccVOO2nMXkQLGgy0He3EQEwKD7NF0zEk+/OWmroznkqXyJdN6bfK0LtNnr6/14Bh3FjpYq7bP33l/VloCnxpA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxfmt/binding-linux-s390x-gnu@0.33.0': - resolution: {integrity: sha512-+mH8cQTqq+Tu2CdoB2/Wmk9CqotXResi+gPvXpb+AAUt/LiwpicTQqSolMheQKogkDTYHPuUiSN23QYmy7IXNQ==} + '@oxfmt/binding-linux-s390x-gnu@0.46.0': + resolution: {integrity: sha512-aAf7fG23OQCey6VRPj9IeCraoYtpgtx0ZyJ1CXkPyT1wjzBE7c3xtuxHe/AdHaJfVVb/SXpSk8Gl1LzyQupSqw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-x64-gnu@0.33.0': - resolution: {integrity: sha512-fjyslAYAPE2+B6Ckrs5LuDQ6lB1re5MumPnzefAXsen3JGwiRilra6XdjUmszTNoExJKbewoxxd6bcLSTpkAJQ==} + '@oxfmt/binding-linux-x64-gnu@0.46.0': + resolution: {integrity: sha512-q0JPsTMyJNjYrBvYFDz4WbVsafNZaPCZv4RnFypRotLqpKROtBZcEaXQW4eb9YmvLU3NckVemLJnzkSZSdmOxw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-x64-musl@0.33.0': - resolution: {integrity: sha512-ve/jGBlTt35Jl/I0A0SfCQX3wKnadzPDdyOFEwe2ZgHHIT9uhqhAv1PaVXTenSBpauICEWYH8mWy+ittzlVE/A==} + '@oxfmt/binding-linux-x64-musl@0.46.0': + resolution: {integrity: sha512-7LsLY9Cw57GPkhSR+duI3mt9baRczK/DtHYSldQ4BEU92da9igBQNl4z7Vq5U9NNPsh1FmpKvv1q9WDtiUQR1A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxfmt/binding-openharmony-arm64@0.33.0': - resolution: {integrity: sha512-lsWRgY9e+uPvwXnuDiJkmJ2Zs3XwwaQkaALJ3/SXU9kjZP0Qh8/tGW8Tk/Z6WL32sDxx+aOK5HuU7qFY9dHJhg==} + '@oxfmt/binding-openharmony-arm64@0.46.0': + resolution: {integrity: sha512-lHiBOz8Duaku7JtRNLlps3j++eOaICPZSd8FCVmTDM4DFOPT71Bjn7g6iar1z7StXlKRweUKxWUs4sA+zWGDXg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxfmt/binding-win32-arm64-msvc@0.33.0': - resolution: {integrity: sha512-w8AQHyGDRZutxtQ7IURdBEddwFrtHQiG6+yIFpNJ4HiMyYEqeAWzwBQBfwSAxtSNh6Y9qqbbc1OM2mHN6AB3Uw==} + '@oxfmt/binding-win32-arm64-msvc@0.46.0': + resolution: {integrity: sha512-/5ktYUliP89RhgC37DBH1x20U5zPSZMy3cMEcO0j3793rbHP9MWsknBwQB6eozRzWmYrh0IFM/p20EbPvDlYlg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxfmt/binding-win32-ia32-msvc@0.33.0': - resolution: {integrity: sha512-j2X4iumKVwDzQtUx3JBDkaydx6eLuncgUZPl2ybZ8llxJMFbZIniws70FzUQePMfMtzLozIm7vo4bjkvQFsOzw==} + '@oxfmt/binding-win32-ia32-msvc@0.46.0': + resolution: {integrity: sha512-3WTnoiuIr8XvV0DIY7SN+1uJSwKf4sPpcbHfobcRT9JutGcLaef/miyBB87jxd3aqH+mS0+G5lsgHuXLUwjjpQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxfmt/binding-win32-x64-msvc@0.33.0': - resolution: {integrity: sha512-lsBQxbepASwOBUh3chcKAjU+jVAQhLElbPYiagIq26cU8vA9Bttj6t20bMvCQCw31m440IRlNhrK7NpnUI8mzA==} + '@oxfmt/binding-win32-x64-msvc@0.46.0': + resolution: {integrity: sha512-IXxiQpkYnOwNfP23vzwSfhdpxJzyiPTY7eTn6dn3DsriKddESzM8i6kfq9R7CD/PUJwCvQT22NgtygBeug3KoA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@oxlint/binding-android-arm-eabi@1.58.0': - resolution: {integrity: sha512-1T7UN3SsWWxpWyWGn1cT3ASNJOo+pI3eUkmEl7HgtowapcV8kslYpFQcYn431VuxghXakPNlbjRwhqmR37PFOg==} + '@oxlint/binding-android-arm-eabi@1.61.0': + resolution: {integrity: sha512-6eZBPgiigK5txqoVgRqxbaxiom4lM8AP8CyKPPvpzKnQ3iFRFOIDc+0AapF+qsUSwjOzr5SGk4SxQDpQhkSJMQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxlint/binding-android-arm64@1.58.0': - resolution: {integrity: sha512-GryzujxuiRv2YFF7bRy8mKcxlbuAN+euVUtGJt9KKbLT8JBUIosamVhcthLh+VEr6KE6cjeVMAQxKAzJcoN7dg==} + '@oxlint/binding-android-arm64@1.61.0': + resolution: {integrity: sha512-CkwLR69MUnyv5wjzebvbbtTSUwqLxM35CXE79bHqDIK+NtKmPEUpStTcLQRZMCo4MP0qRT6TXIQVpK0ZVScnMA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxlint/binding-darwin-arm64@1.58.0': - resolution: {integrity: sha512-7/bRSJIwl4GxeZL9rPZ11anNTyUO9epZrfEJH/ZMla3+/gbQ6xZixh9nOhsZ0QwsTW7/5J2A/fHbD1udC5DQQA==} + '@oxlint/binding-darwin-arm64@1.61.0': + resolution: {integrity: sha512-8JbefTkbmvqkqWjmQrHke+MdpgT2UghhD/ktM4FOQSpGeCgbMToJEKdl9zwhr/YWTl92i4QI1KiTwVExpcUN8A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxlint/binding-darwin-x64@1.58.0': - resolution: {integrity: sha512-EqdtJSiHweS2vfILNrpyJ6HUwpEq2g7+4Zx1FPi4hu3Hu7tC3znF6ufbXO8Ub2LD4mGgznjI7kSdku9NDD1Mkg==} + '@oxlint/binding-darwin-x64@1.61.0': + resolution: {integrity: sha512-uWpoxDT47hTnDLcdEh5jVbso8rlTTu5o0zuqa9J8E0JAKmIWn7kGFEIB03Pycn2hd2vKxybPGLhjURy/9We5FQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxlint/binding-freebsd-x64@1.58.0': - resolution: {integrity: sha512-VQt5TH4M42mY20F545G637RKxV/yjwVtKk2vfXuazfReSIiuvWBnv+FVSvIV5fKVTJNjt3GSJibh6JecbhGdBw==} + '@oxlint/binding-freebsd-x64@1.61.0': + resolution: {integrity: sha512-K/o4hEyW7flfMel0iBVznmMBt7VIMHGdjADocHKpK1DUF9erpWnJ+BSSWd2W0c8K3mPtpph+CuHzRU6CI3l9jQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxlint/binding-linux-arm-gnueabihf@1.58.0': - resolution: {integrity: sha512-fBYcj4ucwpAtjJT3oeBdFBYKvNyjRSK+cyuvBOTQjh0jvKp4yeA4S/D0IsCHus/VPaNG5L48qQkh+Vjy3HL2/Q==} + '@oxlint/binding-linux-arm-gnueabihf@1.61.0': + resolution: {integrity: sha512-P6040ZkcyweJ0Po9yEFqJCdvZnf3VNCGs1SIHgXDf8AAQNC6ID/heXQs9iSgo2FH7gKaKq32VWc59XZwL34C5Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm-musleabihf@1.58.0': - resolution: {integrity: sha512-0BeuFfwlUHlJ1xpEdSD1YO3vByEFGPg36uLjK1JgFaxFb4W6w17F8ET8sz5cheZ4+x5f2xzdnRrrWv83E3Yd8g==} + '@oxlint/binding-linux-arm-musleabihf@1.61.0': + resolution: {integrity: sha512-bwxrGCzTZkuB+THv2TQ1aTkVEfv5oz8sl+0XZZCpoYzErJD8OhPQOTA0ENPd1zJz8QsVdSzSrS2umKtPq4/JXg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm64-gnu@1.58.0': - resolution: {integrity: sha512-TXlZgnPTlxrQzxG9ZXU7BNwx1Ilrr17P3GwZY0If2EzrinqRH3zXPc3HrRcBJgcsoZNMuNL5YivtkJYgp467UQ==} + '@oxlint/binding-linux-arm64-gnu@1.61.0': + resolution: {integrity: sha512-vkhb9/wKguMkLlrm3FoJW/Xmdv31GgYAE+x8lxxQ+7HeOxXUySI0q36a3NTVIuQUdLzxCI1zzMGsk1o37FOe3w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-arm64-musl@1.58.0': - resolution: {integrity: sha512-zSoYRo5dxHLcUx93Stl2hW3hSNjPt99O70eRVWt5A1zwJ+FPjeCCANCD2a9R4JbHsdcl11TIQOjyigcRVOH2mw==} + '@oxlint/binding-linux-arm64-musl@1.61.0': + resolution: {integrity: sha512-bl1dQh8LnVqsj6oOQAcxwbuOmNJkwc4p6o//HTBZhNTzJy21TLDwAviMqUFNUxDHkPGpmdKTSN4tWTjLryP8xg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxlint/binding-linux-ppc64-gnu@1.58.0': - resolution: {integrity: sha512-NQ0U/lqxH2/VxBYeAIvMNUK1y0a1bJ3ZicqkF2c6wfakbEciP9jvIE4yNzCFpZaqeIeRYaV7AVGqEO1yrfVPjA==} + '@oxlint/binding-linux-ppc64-gnu@1.61.0': + resolution: {integrity: sha512-QoOX6KB2IiEpyOj/HKqaxi+NQHPnOgNgnr22n9N4ANJCzXkUlj1UmeAbFb4PpqdlHIzvGDM5xZ0OKtcLq9RhiQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-gnu@1.58.0': - resolution: {integrity: sha512-X9J+kr3gIC9FT8GuZt0ekzpNUtkBVzMVU4KiKDSlocyQuEgi3gBbXYN8UkQiV77FTusLDPsovjo95YedHr+3yg==} + '@oxlint/binding-linux-riscv64-gnu@1.61.0': + resolution: {integrity: sha512-1TGcTerjY6p152wCof3oKElccq3xHljS/Mucp04gV/4ATpP6nO7YNnp7opEg6SHkv2a57/b4b8Ndm9znJ1/qAw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-musl@1.58.0': - resolution: {integrity: sha512-CDze3pi1OO3Wvb/QsXjmLEY4XPKGM6kIo82ssNOgmcl1IdndF9VSGAE38YLhADWmOac7fjqhBw82LozuUVxD0Q==} + '@oxlint/binding-linux-riscv64-musl@1.61.0': + resolution: {integrity: sha512-65wXEmZIrX2ADwC8i/qFL4EWLSbeuBpAm3suuX1vu4IQkKd+wLT/HU/BOl84kp91u2SxPkPDyQgu4yrqp8vwVA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxlint/binding-linux-s390x-gnu@1.58.0': - resolution: {integrity: sha512-b/89glbxFaEAcA6Uf1FvCNecBJEgcUTsV1quzrqXM/o4R1M4u+2KCVuyGCayN2UpsRWtGGLb+Ver0tBBpxaPog==} + '@oxlint/binding-linux-s390x-gnu@1.61.0': + resolution: {integrity: sha512-TVvhgMvor7Qa6COeXxCJ7ENOM+lcAOGsQ0iUdPSCv2hxb9qSHLQ4XF1h50S6RE1gBOJ0WV3rNukg4JJJP1LWRA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-gnu@1.58.0': - resolution: {integrity: sha512-0/yYpkq9VJFCEcuRlrViGj8pJUFFvNS4EkEREaN7CB1EcLXJIaVSSa5eCihwBGXtOZxhnblWgxks9juRdNQI7w==} + '@oxlint/binding-linux-x64-gnu@1.61.0': + resolution: {integrity: sha512-SjpS5uYuFoDnDdZPwZE59ndF95AsY47R5MliuneTWR1pDm2CxGJaYXbKULI71t5TVfLQUWmrHEGRL9xvuq6dnA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-musl@1.58.0': - resolution: {integrity: sha512-hr6FNvmcAXiH+JxSvaJ4SJ1HofkdqEElXICW9sm3/Rd5eC3t7kzvmLyRAB3NngKO2wzXRCAm4Z/mGWfrsS4X8w==} + '@oxlint/binding-linux-x64-musl@1.61.0': + resolution: {integrity: sha512-gGfAeGD4sNJGILZbc/yKcIimO9wQnPMoYp9swAaKeEtwsSQAbU+rsdQze5SBtIP6j0QDzeYd4XSSUCRCF+LIeQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxlint/binding-openharmony-arm64@1.58.0': - resolution: {integrity: sha512-R+O368VXgRql1K6Xar+FEo7NEwfo13EibPMoTv3sesYQedRXd6m30Dh/7lZMxnrQVFfeo4EOfYIP4FpcgWQNHg==} + '@oxlint/binding-openharmony-arm64@1.61.0': + resolution: {integrity: sha512-OlVT0LrG/ct33EVtWRyR+B/othwmDWeRxfi13wUdPeb3lAT5TgTcFDcfLfarZtzB4W1nWF/zICMgYdkggX2WmQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxlint/binding-win32-arm64-msvc@1.58.0': - resolution: {integrity: sha512-Q0FZiAY/3c4YRj4z3h9K1PgaByrifrfbBoODSeX7gy97UtB7pySPUQfC2B/GbxWU6k7CzQrRy5gME10PltLAFQ==} + '@oxlint/binding-win32-arm64-msvc@1.61.0': + resolution: {integrity: sha512-vI//NZPJk6DToiovPtaiwD4iQ7kO1r5ReWQD0sOOyKRtP3E2f6jxin4uvwi3OvDzHA2EFfd7DcZl5dtkQh7g1w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxlint/binding-win32-ia32-msvc@1.58.0': - resolution: {integrity: sha512-Y8FKBABrSPp9H0QkRLHDHOSUgM/309a3IvOVgPcVxYcX70wxJrk608CuTg7w+C6vEd724X5wJoNkBcGYfH7nNQ==} + '@oxlint/binding-win32-ia32-msvc@1.61.0': + resolution: {integrity: sha512-0ySj4/4zd2XjePs3XAQq7IigIstN4LPQZgCyigX5/ERMLjdWAJfnxcTsrtxZxuij8guJW8foXuHmhGxW0H4dDA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxlint/binding-win32-x64-msvc@1.58.0': - resolution: {integrity: sha512-bCn5rbiz5My+Bj7M09sDcnqW0QJyINRVxdZ65x1/Y2tGrMwherwK/lpk+HRQCKvXa8pcaQdF5KY5j54VGZLwNg==} + '@oxlint/binding-win32-x64-msvc@1.61.0': + resolution: {integrity: sha512-0xgSiyeqDLDZxXoe9CVJrOx3TUVsfyoOY7cNi03JbItNcC9WCZqrSNdrAbHONxhSPaVh/lzfnDcON1RqSUMhHw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -5622,13 +5622,13 @@ packages: oxc-resolver@11.19.1: resolution: {integrity: sha512-qE/CIg/spwrTBFt5aKmwe3ifeDdLfA2NESN30E42X/lII5ClF8V7Wt6WIJhcGZjp0/Q+nQ+9vgxGk//xZNX2hg==} - oxfmt@0.33.0: - resolution: {integrity: sha512-ogxBXA9R4BFeo8F1HeMIIxHr5kGnQwKTYZ5k131AEGOq1zLxInNhvYSpyRQ+xIXVMYfCN7yZHKff/lb5lp4auQ==} + oxfmt@0.46.0: + resolution: {integrity: sha512-CopwJOwPAjZ9p76fCvz+mSOJTw9/NY3cSksZK3VO/bUQ8UoEcketNgUuYS0UB3p+R9XnXe7wGGXUmyFxc7QxJA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - oxlint@1.58.0: - resolution: {integrity: sha512-t4s9leczDMqlvOSjnbCQe7gtoLkWgBGZ7sBdCJ9EOj5IXFSG/X7OAzK4yuH4iW+4cAYe8kLFbC8tuYMwWZm+Cg==} + oxlint@1.61.0: + resolution: {integrity: sha512-ZC0ALuhDZ6ivOFG+sy0D0pEDN49EvsId98zVlmYdkcXHsEM14m/qTNUEsUpiFiCVbpIxYtVBmmLE87nsbUHohQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -7864,118 +7864,118 @@ snapshots: '@oxc-resolver/binding-win32-x64-msvc@11.19.1': optional: true - '@oxfmt/binding-android-arm-eabi@0.33.0': + '@oxfmt/binding-android-arm-eabi@0.46.0': optional: true - '@oxfmt/binding-android-arm64@0.33.0': + '@oxfmt/binding-android-arm64@0.46.0': optional: true - '@oxfmt/binding-darwin-arm64@0.33.0': + '@oxfmt/binding-darwin-arm64@0.46.0': optional: true - '@oxfmt/binding-darwin-x64@0.33.0': + '@oxfmt/binding-darwin-x64@0.46.0': optional: true - '@oxfmt/binding-freebsd-x64@0.33.0': + '@oxfmt/binding-freebsd-x64@0.46.0': optional: true - '@oxfmt/binding-linux-arm-gnueabihf@0.33.0': + '@oxfmt/binding-linux-arm-gnueabihf@0.46.0': optional: true - '@oxfmt/binding-linux-arm-musleabihf@0.33.0': + '@oxfmt/binding-linux-arm-musleabihf@0.46.0': optional: true - '@oxfmt/binding-linux-arm64-gnu@0.33.0': + '@oxfmt/binding-linux-arm64-gnu@0.46.0': optional: true - '@oxfmt/binding-linux-arm64-musl@0.33.0': + '@oxfmt/binding-linux-arm64-musl@0.46.0': optional: true - '@oxfmt/binding-linux-ppc64-gnu@0.33.0': + '@oxfmt/binding-linux-ppc64-gnu@0.46.0': optional: true - '@oxfmt/binding-linux-riscv64-gnu@0.33.0': + '@oxfmt/binding-linux-riscv64-gnu@0.46.0': optional: true - '@oxfmt/binding-linux-riscv64-musl@0.33.0': + '@oxfmt/binding-linux-riscv64-musl@0.46.0': optional: true - '@oxfmt/binding-linux-s390x-gnu@0.33.0': + '@oxfmt/binding-linux-s390x-gnu@0.46.0': optional: true - '@oxfmt/binding-linux-x64-gnu@0.33.0': + '@oxfmt/binding-linux-x64-gnu@0.46.0': optional: true - '@oxfmt/binding-linux-x64-musl@0.33.0': + '@oxfmt/binding-linux-x64-musl@0.46.0': optional: true - '@oxfmt/binding-openharmony-arm64@0.33.0': + '@oxfmt/binding-openharmony-arm64@0.46.0': optional: true - '@oxfmt/binding-win32-arm64-msvc@0.33.0': + '@oxfmt/binding-win32-arm64-msvc@0.46.0': optional: true - '@oxfmt/binding-win32-ia32-msvc@0.33.0': + '@oxfmt/binding-win32-ia32-msvc@0.46.0': optional: true - '@oxfmt/binding-win32-x64-msvc@0.33.0': + '@oxfmt/binding-win32-x64-msvc@0.46.0': optional: true - '@oxlint/binding-android-arm-eabi@1.58.0': + '@oxlint/binding-android-arm-eabi@1.61.0': optional: true - '@oxlint/binding-android-arm64@1.58.0': + '@oxlint/binding-android-arm64@1.61.0': optional: true - '@oxlint/binding-darwin-arm64@1.58.0': + '@oxlint/binding-darwin-arm64@1.61.0': optional: true - '@oxlint/binding-darwin-x64@1.58.0': + '@oxlint/binding-darwin-x64@1.61.0': optional: true - '@oxlint/binding-freebsd-x64@1.58.0': + '@oxlint/binding-freebsd-x64@1.61.0': optional: true - '@oxlint/binding-linux-arm-gnueabihf@1.58.0': + '@oxlint/binding-linux-arm-gnueabihf@1.61.0': optional: true - '@oxlint/binding-linux-arm-musleabihf@1.58.0': + '@oxlint/binding-linux-arm-musleabihf@1.61.0': optional: true - '@oxlint/binding-linux-arm64-gnu@1.58.0': + '@oxlint/binding-linux-arm64-gnu@1.61.0': optional: true - '@oxlint/binding-linux-arm64-musl@1.58.0': + '@oxlint/binding-linux-arm64-musl@1.61.0': optional: true - '@oxlint/binding-linux-ppc64-gnu@1.58.0': + '@oxlint/binding-linux-ppc64-gnu@1.61.0': optional: true - '@oxlint/binding-linux-riscv64-gnu@1.58.0': + '@oxlint/binding-linux-riscv64-gnu@1.61.0': optional: true - '@oxlint/binding-linux-riscv64-musl@1.58.0': + '@oxlint/binding-linux-riscv64-musl@1.61.0': optional: true - '@oxlint/binding-linux-s390x-gnu@1.58.0': + '@oxlint/binding-linux-s390x-gnu@1.61.0': optional: true - '@oxlint/binding-linux-x64-gnu@1.58.0': + '@oxlint/binding-linux-x64-gnu@1.61.0': optional: true - '@oxlint/binding-linux-x64-musl@1.58.0': + '@oxlint/binding-linux-x64-musl@1.61.0': optional: true - '@oxlint/binding-openharmony-arm64@1.58.0': + '@oxlint/binding-openharmony-arm64@1.61.0': optional: true - '@oxlint/binding-win32-arm64-msvc@1.58.0': + '@oxlint/binding-win32-arm64-msvc@1.61.0': optional: true - '@oxlint/binding-win32-ia32-msvc@1.58.0': + '@oxlint/binding-win32-ia32-msvc@1.61.0': optional: true - '@oxlint/binding-win32-x64-msvc@1.58.0': + '@oxlint/binding-win32-x64-msvc@1.61.0': optional: true '@playwright/test@1.59.0': @@ -11405,51 +11405,51 @@ snapshots: - '@emnapi/core' - '@emnapi/runtime' - oxfmt@0.33.0: + oxfmt@0.46.0: dependencies: tinypool: 2.1.0 optionalDependencies: - '@oxfmt/binding-android-arm-eabi': 0.33.0 - '@oxfmt/binding-android-arm64': 0.33.0 - '@oxfmt/binding-darwin-arm64': 0.33.0 - '@oxfmt/binding-darwin-x64': 0.33.0 - '@oxfmt/binding-freebsd-x64': 0.33.0 - '@oxfmt/binding-linux-arm-gnueabihf': 0.33.0 - '@oxfmt/binding-linux-arm-musleabihf': 0.33.0 - '@oxfmt/binding-linux-arm64-gnu': 0.33.0 - '@oxfmt/binding-linux-arm64-musl': 0.33.0 - '@oxfmt/binding-linux-ppc64-gnu': 0.33.0 - '@oxfmt/binding-linux-riscv64-gnu': 0.33.0 - '@oxfmt/binding-linux-riscv64-musl': 0.33.0 - '@oxfmt/binding-linux-s390x-gnu': 0.33.0 - '@oxfmt/binding-linux-x64-gnu': 0.33.0 - '@oxfmt/binding-linux-x64-musl': 0.33.0 - '@oxfmt/binding-openharmony-arm64': 0.33.0 - '@oxfmt/binding-win32-arm64-msvc': 0.33.0 - '@oxfmt/binding-win32-ia32-msvc': 0.33.0 - '@oxfmt/binding-win32-x64-msvc': 0.33.0 - - oxlint@1.58.0: + '@oxfmt/binding-android-arm-eabi': 0.46.0 + '@oxfmt/binding-android-arm64': 0.46.0 + '@oxfmt/binding-darwin-arm64': 0.46.0 + '@oxfmt/binding-darwin-x64': 0.46.0 + '@oxfmt/binding-freebsd-x64': 0.46.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.46.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.46.0 + '@oxfmt/binding-linux-arm64-gnu': 0.46.0 + '@oxfmt/binding-linux-arm64-musl': 0.46.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.46.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.46.0 + '@oxfmt/binding-linux-riscv64-musl': 0.46.0 + '@oxfmt/binding-linux-s390x-gnu': 0.46.0 + '@oxfmt/binding-linux-x64-gnu': 0.46.0 + '@oxfmt/binding-linux-x64-musl': 0.46.0 + '@oxfmt/binding-openharmony-arm64': 0.46.0 + '@oxfmt/binding-win32-arm64-msvc': 0.46.0 + '@oxfmt/binding-win32-ia32-msvc': 0.46.0 + '@oxfmt/binding-win32-x64-msvc': 0.46.0 + + oxlint@1.61.0: optionalDependencies: - '@oxlint/binding-android-arm-eabi': 1.58.0 - '@oxlint/binding-android-arm64': 1.58.0 - '@oxlint/binding-darwin-arm64': 1.58.0 - '@oxlint/binding-darwin-x64': 1.58.0 - '@oxlint/binding-freebsd-x64': 1.58.0 - '@oxlint/binding-linux-arm-gnueabihf': 1.58.0 - '@oxlint/binding-linux-arm-musleabihf': 1.58.0 - '@oxlint/binding-linux-arm64-gnu': 1.58.0 - '@oxlint/binding-linux-arm64-musl': 1.58.0 - '@oxlint/binding-linux-ppc64-gnu': 1.58.0 - '@oxlint/binding-linux-riscv64-gnu': 1.58.0 - '@oxlint/binding-linux-riscv64-musl': 1.58.0 - '@oxlint/binding-linux-s390x-gnu': 1.58.0 - '@oxlint/binding-linux-x64-gnu': 1.58.0 - '@oxlint/binding-linux-x64-musl': 1.58.0 - '@oxlint/binding-openharmony-arm64': 1.58.0 - '@oxlint/binding-win32-arm64-msvc': 1.58.0 - '@oxlint/binding-win32-ia32-msvc': 1.58.0 - '@oxlint/binding-win32-x64-msvc': 1.58.0 + '@oxlint/binding-android-arm-eabi': 1.61.0 + '@oxlint/binding-android-arm64': 1.61.0 + '@oxlint/binding-darwin-arm64': 1.61.0 + '@oxlint/binding-darwin-x64': 1.61.0 + '@oxlint/binding-freebsd-x64': 1.61.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.61.0 + '@oxlint/binding-linux-arm-musleabihf': 1.61.0 + '@oxlint/binding-linux-arm64-gnu': 1.61.0 + '@oxlint/binding-linux-arm64-musl': 1.61.0 + '@oxlint/binding-linux-ppc64-gnu': 1.61.0 + '@oxlint/binding-linux-riscv64-gnu': 1.61.0 + '@oxlint/binding-linux-riscv64-musl': 1.61.0 + '@oxlint/binding-linux-s390x-gnu': 1.61.0 + '@oxlint/binding-linux-x64-gnu': 1.61.0 + '@oxlint/binding-linux-x64-musl': 1.61.0 + '@oxlint/binding-openharmony-arm64': 1.61.0 + '@oxlint/binding-win32-arm64-msvc': 1.61.0 + '@oxlint/binding-win32-ia32-msvc': 1.61.0 + '@oxlint/binding-win32-x64-msvc': 1.61.0 p-limit@3.1.0: dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index c6a79718b9..81ac089f11 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -48,8 +48,8 @@ catalog: "next-themes": "^0.4.6" "next-validate-link": "^1.6.3" "npm-to-yarn": "^3.0.1" - "oxfmt": "^0.33.0" - "oxlint": "^1.48.0" + "oxfmt": "^0.46.0" + "oxlint": "^1.61.0" "postcss": "^8.5.6" "posthog-js": "^1.351.3" "react": "^19.2.4" From 783dd258bb099dcc07cb6c25698fd9485f7bf4e7 Mon Sep 17 00:00:00 2001 From: Aman Varshney Date: Wed, 22 Apr 2026 17:53:01 +0530 Subject: [PATCH 2/4] style: apply oxfmt 0.46 formatting across repo --- .../index.mdx | 54 +- .../index.mdx | 14 +- .../blog/accelerate-ipv6-first/index.mdx | 3 +- .../index.mdx | 34 +- .../accelerate-static-ip-support/index.mdx | 45 +- .../index.mdx | 57 +- .../index.mdx | 65 +- .../ambassador-program-nxkWGcGNuvFx/index.mdx | 9 +- .../index.mdx | 16 +- .../index.mdx | 2 - .../announcing-discord-1LiAOpS7lxV9/index.mdx | 3 +- .../index.mdx | 114 +- .../index.mdx | 324 +- .../index.mdx | 79 +- .../blog/announcing-prisma-6-18-0/index.mdx | 31 +- .../blog/announcing-prisma-6-19-0/index.mdx | 114 +- .../index.mdx | 7 +- .../announcing-prisma-orm-7-0-0/index.mdx | 55 +- .../announcing-prisma-orm-7-2-0/index.mdx | 7 +- .../index.mdx | 2 - .../index.mdx | 38 +- .../index.mdx | 5 +- .../index.mdx | 18 +- .../index.mdx | 12 +- .../index.mdx | 167 +- .../index.mdx | 85 +- .../index.mdx | 3 +- .../content/blog/aws-marketplace/index.mdx | 13 +- .../index.mdx | 318 +- .../index.mdx | 163 +- .../index.mdx | 45 +- .../index.mdx | 571 +- .../index.mdx | 9 +- apps/blog/content/blog/bfg/index.mdx | 18 +- .../index.mdx | 9 +- .../index.mdx | 99 +- .../index.mdx | 69 +- .../index.mdx | 14 +- .../index.mdx | 32 +- .../index.mdx | 595 +- .../cloud-connectivity-report-2024/index.mdx | 3 +- .../index.mdx | 1 - .../index.mdx | 35 +- .../blog/cockroach-ga-5JrD9XVWQDYL/index.mdx | 13 +- .../blog/compliance-reqs-complete/index.mdx | 4 +- .../index.mdx | 2 +- .../index.mdx | 5 +- apps/blog/content/blog/convergence/index.mdx | 102 +- .../coo-announcement-aer1fgviirjb/index.mdx | 3 +- .../blog/data-platform-static-ips/index.mdx | 9 +- .../index.mdx | 100 +- .../index.mdx | 19 +- .../index.mdx | 44 +- .../datadx-event-recap-z5pcp6hzbz5m/index.mdx | 23 +- .../datadx-manifesto-ikgyqj170k8h/index.mdx | 5 +- .../index.mdx | 8 +- .../blog/datamodel-v11-lrzqy1f56c90/index.mdx | 85 +- .../documenting-apis-mjjpZ7E7NkVP/index.mdx | 187 +- .../index.mdx | 180 +- .../index.mdx | 45 +- .../index.mdx | 115 +- .../index.mdx | 155 +- .../index.mdx | 14 +- .../index.mdx | 95 +- .../blog/esop-exercise-windows/index.mdx | 23 +- .../index.mdx | 10 +- .../index.mdx | 12 +- .../index.mdx | 183 +- .../index.mdx | 292 +- .../index.mdx | 290 +- .../index.mdx | 138 +- .../index.mdx | 7 +- .../index.mdx | 125 +- .../index.mdx | 49 +- .../index.mdx | 425 +- .../index.mdx | 453 +- .../index.mdx | 251 +- .../index.mdx | 2 +- .../index.mdx | 212 +- .../index.mdx | 53 +- .../blog/graphql-eu-18-eiw8bishe2di/index.mdx | 5 +- .../graphql-middleware-zie3iphithxy/index.mdx | 90 +- .../index.mdx | 94 +- .../index.mdx | 11 +- .../index.mdx | 93 +- .../index.mdx | 73 +- .../index.mdx | 47 +- .../index.mdx | 27 +- .../index.mdx | 9 +- .../index.mdx | 54 +- .../blog/how-bucket-uses-prisma-orm/index.mdx | 3 +- .../index.mdx | 40 +- .../index.mdx | 8 +- .../index.mdx | 58 +- .../index.mdx | 8 +- .../index.mdx | 25 +- .../index.mdx | 48 +- .../index.mdx | 48 +- .../index.mdx | 45 +- .../index.mdx | 53 +- .../index.mdx | 44 +- .../index.mdx | 15 +- .../index.mdx | 115 +- .../index.mdx | 48 +- .../index.mdx | 120 +- .../index.mdx | 63 +- .../index.mdx | 17 +- .../index.mdx | 3 +- .../index.mdx | 18 +- .../index.mdx | 81 +- .../index.mdx | 76 +- .../index.mdx | 8 +- .../index.mdx | 7 +- .../index.mdx | 137 +- .../index.mdx | 31 +- .../index.mdx | 45 +- .../blog/introducing-prisma-nuxt/index.mdx | 136 +- .../index.mdx | 2 +- .../index.mdx | 37 +- .../blog/labelbox-simnycbotiok/index.mdx | 3 +- .../index.mdx | 346 +- .../index.mdx | 55 +- .../index.mdx | 104 +- .../index.mdx | 37 +- .../blog/mongodb-without-compromise/index.mdx | 57 +- .../index.mdx | 163 +- .../index.mdx | 66 +- .../index.mdx | 99 +- .../index.mdx | 163 +- .../index.mdx | 115 +- .../index.mdx | 10 +- .../blog/operations-based-billing/index.mdx | 12 +- .../index.mdx | 2 +- .../index.mdx | 17 +- .../index.mdx | 38 +- .../index.mdx | 20 +- .../index.mdx | 40 +- .../index.mdx | 13 +- .../index.mdx | 59 +- .../index.mdx | 39 +- .../index.mdx | 34 +- .../index.mdx | 8 +- .../index.mdx | 21 +- .../index.mdx | 9 +- .../index.mdx | 10 +- .../index.mdx | 22 +- .../blog/prisma-2-beta-b7bcl0gd8d8e/index.mdx | 31 +- .../blog/prisma-5-f66prwkjx72s/index.mdx | 22 +- .../blog/prisma-6-8-0-release/index.mdx | 24 +- .../blog/prisma-6-9-0-release/index.mdx | 27 +- .../index.mdx | 58 +- .../index.mdx | 14 +- .../prisma-7-performance-benchmarks/index.mdx | 11 +- .../prisma-adopts-semver-strictly/index.mdx | 1 - .../prisma-and-graphql-mfl5y2r7t49c/index.mdx | 3 +- .../index.mdx | 62 +- .../index.mdx | 74 +- .../index.mdx | 10 +- .../prisma-data-proxy-xb16ba0p21/index.mdx | 6 +- .../index.mdx | 33 +- .../index.mdx | 13 +- .../blog/prisma-insider-program/index.mdx | 4 +- .../index.mdx | 18 +- .../prisma-migrate-dx-primitives/index.mdx | 9 +- .../prisma-migrate-ga-b5eno5g08d0b/index.mdx | 53 +- .../index.mdx | 25 +- .../prisma-mongodb-preview-release/index.mdx | 5 +- .../index.mdx | 11 +- .../prisma-optimize-early-access/index.mdx | 19 +- .../index.mdx | 49 +- .../content/blog/prisma-orm-7-3-0/index.mdx | 2 - .../blog/prisma-orm-manifesto/index.mdx | 12 +- .../index.mdx | 21 +- .../index.mdx | 59 +- .../index.mdx | 9 +- .../index.mdx | 20 +- .../index.mdx | 5 +- .../index.mdx | 6 +- .../index.mdx | 7 +- .../index.mdx | 68 +- .../index.mdx | 24 +- .../index.mdx | 5 +- .../index.mdx | 22 +- .../index.mdx | 139 +- .../index.mdx | 9 +- .../blog/prisma-startup-program/index.mdx | 1 - .../blog/prisma-studio-3rtf78dg99fe/index.mdx | 28 +- .../index.mdx | 99 +- .../index.mdx | 138 +- .../index.mdx | 64 +- .../blog/rebuilding-the-prisma-docs/index.mdx | 8 +- .../index.mdx | 13 +- .../index.mdx | 109 +- .../index.mdx | 11 +- .../rethinking-database-migrations/index.mdx | 18 +- .../index.mdx | 9 +- .../index.mdx | 30 +- .../satisfies-operator-ur8ys8ccq7zb/index.mdx | 52 +- .../index.mdx | 17 +- .../index.mdx | 24 +- .../index.mdx | 27 +- .../index.mdx | 36 +- .../index.mdx | 8 - .../index.mdx | 4 +- .../sveltekit-prisma-kvCOEoeQlC/index.mdx | 368 +- .../testing-series-1-8eRB5p0Y8o/index.mdx | 398 +- .../testing-series-2-xPhjjmIEsM/index.mdx | 431 +- .../testing-series-3-aBUyF8nxAn/index.mdx | 219 +- .../testing-series-4-OVXtDis201/index.mdx | 227 +- .../testing-series-5-xWogenROXm/index.mdx | 128 +- .../index.mdx | 1 - .../index.mdx | 10 +- .../index.mdx | 85 +- .../top-5-myths-about-prisma-orm/index.mdx | 46 +- .../index.mdx | 1 - .../index.mdx | 17 +- .../index.mdx | 198 +- .../index.mdx | 11 +- .../index.mdx | 39 +- .../index.mdx | 6 +- .../index.mdx | 63 +- .../index.mdx | 137 +- .../index.mdx | 126 +- .../blog/tweets-for-trees-arboreal/index.mdx | 15 +- .../index.mdx | 110 +- .../index.mdx | 141 +- .../index.mdx | 5 +- .../index.mdx | 149 +- .../index.mdx | 7 +- .../index.mdx | 1 - .../index.mdx | 6 +- .../index.mdx | 186 +- .../index.mdx | 244 +- .../content/blog/why-prisma-2024/index.mdx | 122 +- .../index.mdx | 34 +- .../index.mdx | 26 +- .../index.mdx | 39 +- .../index.mdx | 9 +- .../content/blog/wnip-q1-dsk0golh8v/index.mdx | 236 +- .../blog/wnip-q2-2022-pmn7rulcj8x/index.mdx | 208 +- .../content/blog/wnip-q3-hpk7pyth8v/index.mdx | 452 +- .../blog/wnip-q4-2022-f66prwkjx72s/index.mdx | 262 +- .../content/blog/wnip-q4-dsk0golh8v/index.mdx | 452 +- .../index.mdx | 33 +- apps/blog/next.config.mjs | 4 +- apps/blog/package.json | 12 +- apps/blog/scripts/lint.ts | 32 +- apps/blog/source.config.ts | 26 +- apps/blog/src/app/(blog)/[slug]/page.tsx | 37 +- apps/blog/src/app/(blog)/layout.tsx | 5 +- apps/blog/src/app/(blog)/page.tsx | 13 +- apps/blog/src/app/api/newsletter/route.ts | 19 +- apps/blog/src/app/api/search/route.ts | 2 +- apps/blog/src/app/global.css | 129 +- apps/blog/src/app/layout.tsx | 12 +- apps/blog/src/app/not-found.tsx | 35 +- apps/blog/src/app/og/image.png/route.tsx | 16 +- apps/blog/src/app/robots.ts | 6 +- apps/blog/src/app/rss.xml/route.ts | 2 +- apps/blog/src/app/sitemap.ts | 7 +- .../blog/src/components/AuthorAvatarGroup.tsx | 6 +- apps/blog/src/components/BlogGrid.tsx | 8 +- apps/blog/src/components/BlogShare.tsx | 12 +- .../blog/src/components/CategoryTagFilter.tsx | 86 +- apps/blog/src/components/Employee.tsx | 9 +- apps/blog/src/components/Hex.tsx | 2 +- apps/blog/src/components/Meetup.tsx | 11 +- apps/blog/src/components/PostCard.tsx | 4 +- apps/blog/src/components/Quote.tsx | 6 +- apps/blog/src/components/TweetEmbed.tsx | 48 +- .../src/components/navigation-wrapper.tsx | 11 +- apps/blog/src/components/search-toggle.tsx | 22 +- apps/blog/src/components/search.tsx | 18 +- apps/blog/src/components/utm-persistence.tsx | 4 +- apps/blog/src/lib/authors.ts | 4 +- apps/blog/src/lib/blog-metadata.ts | 3 +- apps/blog/src/lib/rss.ts | 14 +- apps/blog/src/lib/search-types.ts | 16 +- apps/blog/src/lib/source.tsx | 6 +- apps/blog/src/lib/url.ts | 167 +- apps/blog/src/mdx-components.tsx | 10 +- apps/docs/OPENAPI_CACHE.md | 7 +- apps/docs/PROMPT_GUIDE.md | 9 +- apps/docs/README.md | 22 +- apps/docs/content/docs.v6/(index)/index.mdx | 10 +- .../add-to-existing-project/cockroachdb.mdx | 4 +- .../add-to-existing-project/mysql.mdx | 4 +- .../add-to-existing-project/postgresql.mdx | 4 +- .../prisma-postgres.mdx | 4 +- .../add-to-existing-project/sql-server.mdx | 4 +- .../add-to-existing-project/sqlite.mdx | 4 +- .../prisma-orm/quickstart/cockroachdb.mdx | 2 +- .../(index)/prisma-orm/quickstart/mongodb.mdx | 2 +- .../(index)/prisma-orm/quickstart/mysql.mdx | 2 +- .../prisma-orm/quickstart/planetscale.mdx | 2 +- .../prisma-orm/quickstart/postgresql.mdx | 2 +- .../prisma-orm/quickstart/prisma-postgres.mdx | 2 +- .../prisma-orm/quickstart/sql-server.mdx | 2 +- .../(index)/prisma-orm/quickstart/sqlite.mdx | 2 +- .../docs.v6/(index)/prisma-postgres/index.mdx | 4 +- .../quickstart/drizzle-orm.mdx | 2 +- .../prisma-postgres/quickstart/kysely.mdx | 2 +- .../prisma-postgres/quickstart/prisma-orm.mdx | 2 +- .../prisma-postgres/quickstart/typeorm.mdx | 2 +- .../docs.v6/accelerate/api-reference.mdx | 10 +- .../content/docs.v6/accelerate/caching.mdx | 2 +- .../docs.v6/accelerate/connection-pooling.mdx | 2 +- .../content/docs.v6/accelerate/evaluating.mdx | 2 +- .../content/docs.v6/accelerate/examples.mdx | 2 +- apps/docs/content/docs.v6/accelerate/faq.mdx | 2 +- .../content/docs.v6/accelerate/feedback.mdx | 2 +- .../docs.v6/accelerate/known-limitations.mdx | 2 +- .../docs.v6/accelerate/local-development.mdx | 2 +- .../docs.v6/accelerate/troubleshoot.mdx | 2 +- .../docs.v6/ai/tutorials/linktree-clone.mdx | 8 +- .../content/docs.v6/guides/ai-sdk-nextjs.mdx | 6 +- apps/docs/content/docs.v6/guides/data-dog.mdx | 4 +- .../docs.v6/guides/multiple-databases.mdx | 4 +- .../docs/content/docs.v6/guides/turborepo.mdx | 5 +- apps/docs/content/docs.v6/meta.json | 10 +- .../orm/more/ai-tools/github-copilot.mdx | 4 +- .../more/comparisons/prisma-and-mongoose.mdx | 28 +- .../more/comparisons/prisma-and-sequelize.mdx | 81 +- .../orm/more/dev-environment/meta.json | 5 +- .../docs.v6/orm/more/internals/meta.json | 4 +- .../content/docs.v6/orm/more/releases.mdx | 4 +- .../many-to-many-relations.mdx | 2 +- .../orm/more/troubleshooting/nextjs.mdx | 2 +- .../docs.v6/orm/more/troubleshooting/nuxt.mdx | 2 +- .../troubleshooting/raw-sql-comparisons.mdx | 6 +- .../docs.v6/orm/more/upgrades/from-v1.mdx | 26 +- .../docs.v6/orm/more/upgrades/meta.json | 7 +- .../orm/more/upgrades/older-versions.mdx | 5 +- .../orm/overview/beyond-prisma-orm.mdx | 4 +- .../orm/overview/databases/cockroachdb.mdx | 12 +- .../docs.v6/orm/overview/databases/mysql.mdx | 18 +- .../orm/overview/databases/postgresql.mdx | 92 +- .../overview/databases/sql-server/index.mdx | 2 +- .../introduction/should-you-use-prisma.mdx | 4 +- .../client-extensions/client.mdx | 8 +- .../prisma-client/client-extensions/model.mdx | 8 +- .../prisma-client/client-extensions/query.mdx | 8 +- .../client-extensions/result.mdx | 8 +- .../shared-extensions/permit-rbac.mdx | 4 +- .../client-extensions/type-utilities.mdx | 6 +- .../debugging-and-troubleshooting/meta.json | 6 +- .../troubleshooting-binary-size-issues.mdx | 4 +- .../deployment/edge/overview.mdx | 2 +- .../deployment/module-bundlers.mdx | 8 +- .../serverless/deploy-to-aws-lambda.mdx | 4 +- .../deployment/serverless/index.mdx | 4 +- .../deployment/traditional/index.mdx | 4 +- .../sql-comments.mdx | 4 +- .../aggregation-grouping-summarizing.mdx | 8 +- .../queries/filtering-and-sorting.mdx | 4 +- .../queries/relation-queries.mdx | 4 +- .../databases-connections/index.mdx | 4 +- .../databases-connections/pgbouncer.mdx | 4 +- .../generating-prisma-client.mdx | 4 +- .../no-rust-engine.mdx | 197 +- ...ing-with-composite-ids-and-constraints.mdx | 4 +- .../working-with-json-fields.mdx | 6 +- .../working-with-scalar-lists-arrays.mdx | 4 +- .../orm/prisma-client/type-safety/index.mdx | 4 +- .../using-raw-sql/raw-queries.mdx | 32 +- .../prisma-client/using-raw-sql/safeql.mdx | 4 +- .../legacy-migrate.mdx | 34 +- .../limitations-and-known-issues.mdx | 2 +- .../workflows/customizing-migrations.mdx | 66 +- .../unsupported-database-features.mdx | 2 +- .../orm/prisma-schema/data-model/models.mdx | 4 +- .../data-model/relations/index.mdx | 4 +- .../special-rules-for-referential-actions.mdx | 4 +- .../relations/troubleshooting-relations.mdx | 2 +- .../unsupported-database-features.mdx | 2 +- .../prisma-schema/postgresql-extensions.mdx | 4 +- .../docs.v6/orm/reference/connection-urls.mdx | 24 +- .../orm/reference/database-features.mdx | 54 +- .../docs.v6/orm/reference/error-reference.mdx | 4 +- .../docs.v6/orm/reference/errors/index.mdx | 2 +- .../preview-features/cli-preview-features.mdx | 6 +- .../client-preview-features.mdx | 24 +- .../orm/reference/prisma-cli-reference.mdx | 40 +- .../orm/reference/prisma-client-reference.mdx | 140 +- .../orm/reference/prisma-schema-reference.mdx | 58 +- apps/docs/content/docs.v6/platform/index.mdx | 4 +- .../docs.v6/platform/maturity-levels.mdx | 2 +- .../docs.v6/platform/platform-cli/about.mdx | 2 +- .../platform/platform-cli/commands.mdx | 16 +- .../docs/content/docs.v6/platform/support.mdx | 2 +- .../api-reference/error-reference.mdx | 2 +- .../postgres/database/api-reference/index.mdx | 3 +- .../postgres/database/prisma-studio/index.mdx | 4 +- .../postgres/database/serverless-driver.mdx | 4 +- apps/docs/content/docs.v6/postgres/index.mdx | 2 +- .../docs.v6/postgres/integrations/index.mdx | 4 +- .../docs.v6/postgres/introduction/index.mdx | 4 +- .../introduction/management-api-sdk.mdx | 6 +- .../postgres/introduction/management-api.mdx | 2 +- apps/docs/content/docs/(index)/index.mdx | 36 +- .../add-to-existing-project/cockroachdb.mdx | 5 +- .../add-to-existing-project/mysql.mdx | 4 +- .../add-to-existing-project/planetscale.mdx | 10 +- .../add-to-existing-project/postgresql.mdx | 4 +- .../prisma-postgres.mdx | 4 +- .../add-to-existing-project/sql-server.mdx | 4 +- .../add-to-existing-project/sqlite.mdx | 7 +- .../prisma-orm/quickstart/cockroachdb.mdx | 5 +- .../(index)/prisma-orm/quickstart/mongodb.mdx | 5 +- .../(index)/prisma-orm/quickstart/mysql.mdx | 5 +- .../prisma-orm/quickstart/planetscale.mdx | 18 +- .../prisma-orm/quickstart/postgresql.mdx | 5 +- .../prisma-orm/quickstart/prisma-postgres.mdx | 4 +- .../prisma-orm/quickstart/sql-server.mdx | 4 +- .../(index)/prisma-orm/quickstart/sqlite.mdx | 6 +- .../(index)/prisma-postgres/from-the-cli.mdx | 1 - .../import-from-existing-database-mysql.mdx | 3 +- ...port-from-existing-database-postgresql.mdx | 25 +- .../quickstart/drizzle-orm.mdx | 2 +- .../prisma-postgres/quickstart/kysely.mdx | 2 +- .../prisma-postgres/quickstart/prisma-orm.mdx | 2 +- .../prisma-postgres/quickstart/typeorm.mdx | 2 +- apps/docs/content/docs/accelerate/caching.mdx | 2 +- .../docs/accelerate/connection-pooling.mdx | 2 +- .../content/docs/accelerate/evaluating.mdx | 2 +- .../docs/content/docs/accelerate/examples.mdx | 2 +- .../docs/accelerate/getting-started.mdx | 1 - .../docs/accelerate/local-development.mdx | 3 +- .../docs/content/docs/accelerate/more/faq.mdx | 2 +- .../content/docs/accelerate/more/feedback.mdx | 2 +- .../accelerate/more/known-limitations.mdx | 2 +- .../docs/accelerate/more/troubleshoot.mdx | 2 +- .../accelerate/reference/api-reference.mdx | 10 +- apps/docs/content/docs/ai/prompts/nuxt.mdx | 6 +- .../docs/content/docs/ai/prompts/prisma-7.mdx | 4 +- apps/docs/content/docs/ai/tools/cursor.mdx | 4 +- .../content/docs/ai/tools/github-copilot.mdx | 4 +- .../docs/content/docs/ai/tools/mcp-server.mdx | 5 +- apps/docs/content/docs/ai/tools/skills.mdx | 15 +- .../docs/ai/tutorials/linktree-clone.mdx | 21 +- .../docs/ai/tutorials/typefully-clone.mdx | 197 +- apps/docs/content/docs/cli/console/index.mdx | 8 +- apps/docs/content/docs/cli/console/status.mdx | 2 +- apps/docs/content/docs/cli/db/execute.mdx | 2 +- apps/docs/content/docs/cli/db/index.mdx | 6 +- apps/docs/content/docs/cli/db/pull.mdx | 2 +- apps/docs/content/docs/cli/db/push.mdx | 2 +- apps/docs/content/docs/cli/db/seed.mdx | 2 +- apps/docs/content/docs/cli/debug.mdx | 4 +- apps/docs/content/docs/cli/dev/index.mdx | 6 +- apps/docs/content/docs/cli/dev/ls.mdx | 2 +- apps/docs/content/docs/cli/dev/rm.mdx | 2 +- apps/docs/content/docs/cli/dev/start.mdx | 2 +- apps/docs/content/docs/cli/dev/stop.mdx | 2 +- apps/docs/content/docs/cli/format.mdx | 2 +- apps/docs/content/docs/cli/generate.mdx | 2 +- apps/docs/content/docs/cli/index.mdx | 8 +- apps/docs/content/docs/cli/init.mdx | 2 +- apps/docs/content/docs/cli/mcp.mdx | 2 +- apps/docs/content/docs/cli/migrate/deploy.mdx | 2 +- apps/docs/content/docs/cli/migrate/dev.mdx | 4 +- apps/docs/content/docs/cli/migrate/diff.mdx | 6 +- apps/docs/content/docs/cli/migrate/index.mdx | 14 +- apps/docs/content/docs/cli/migrate/reset.mdx | 2 +- .../docs/content/docs/cli/migrate/resolve.mdx | 2 +- apps/docs/content/docs/cli/migrate/status.mdx | 2 +- apps/docs/content/docs/cli/studio.mdx | 2 +- apps/docs/content/docs/cli/validate.mdx | 2 +- apps/docs/content/docs/cli/version.mdx | 4 +- apps/docs/content/docs/console/concepts.mdx | 5 +- .../content/docs/console/features/meta.json | 4 +- .../content/docs/console/features/metrics.mdx | 2 +- .../content/docs/console/getting-started.mdx | 2 +- apps/docs/content/docs/console/index.mdx | 3 +- .../docs/console/more/feature-maturity.mdx | 2 +- apps/docs/content/docs/console/more/meta.json | 5 +- .../content/docs/console/more/support.mdx | 2 +- .../authentication/better-auth/astro.mdx | 34 +- .../guides/database/multiple-databases.mdx | 4 +- .../docs/guides/deployment/bun-workspaces.mdx | 4 +- .../guides/deployment/pnpm-workspaces.mdx | 1 - .../docs/guides/deployment/turborepo.mdx | 13 +- .../content/docs/guides/frameworks/hono.mdx | 2 +- .../content/docs/guides/frameworks/meta.json | 17 +- .../docs/guides/frameworks/react-router-7.mdx | 2 +- .../docs/guides/integrations/ai-sdk.mdx | 6 +- .../docs/guides/integrations/datadog.mdx | 4 +- .../docs/guides/integrations/embed-studio.mdx | 1 - .../docs/guides/integrations/permit-io.mdx | 1 - .../docs/guides/integrations/pgfence.mdx | 12 +- .../docs/guides/integrations/shopify.mdx | 18 +- .../content/docs/guides/making-guides.mdx | 28 +- .../content/docs/guides/runtimes/meta.json | 2 +- .../switch-to-prisma-orm/from-sql-orms.mdx | 52 +- .../docs/guides/upgrade-prisma-orm/meta.json | 9 +- .../docs/guides/upgrade-prisma-orm/v1.mdx | 114 +- .../docs/guides/upgrade-prisma-orm/v3.mdx | 46 +- .../docs/guides/upgrade-prisma-orm/v4.mdx | 30 +- .../docs/guides/upgrade-prisma-orm/v5.mdx | 6 +- .../docs/guides/upgrade-prisma-orm/v6.mdx | 13 +- .../docs/guides/upgrade-prisma-orm/v7.mdx | 8 +- .../docs/management-api/api-clients.mdx | 20 +- .../docs/management-api/authentication.mdx | 41 +- ...compute-services-by-compute-service-id.mdx | 15 +- ...ompute-services-versions-by-version-id.mdx | 15 +- .../delete-versions-by-version-id.mdx | 15 +- ...ervices-by-compute-service-id-versions.mdx | 15 +- ...compute-services-by-compute-service-id.mdx | 15 +- ...e-services-versions-by-version-id-logs.mdx | 11 +- ...ompute-services-versions-by-version-id.mdx | 15 +- .../[experimental]/get-compute-services.mdx | 11 +- ...rojects-by-project-id-compute-services.mdx | 15 +- .../get-versions-by-version-id.mdx | 15 +- .../endpoints/[experimental]/get-versions.mdx | 8 +- ...compute-services-by-compute-service-id.mdx | 15 +- ...services-by-compute-service-id-promote.mdx | 15 +- ...ervices-by-compute-service-id-versions.mdx | 15 +- ...-services-versions-by-version-id-start.mdx | 15 +- ...e-services-versions-by-version-id-stop.mdx | 15 +- .../[experimental]/post-compute-services.mdx | 11 +- ...rojects-by-project-id-compute-services.mdx | 15 +- .../post-versions-by-version-id-start.mdx | 15 +- .../post-versions-by-version-id-stop.mdx | 15 +- .../[experimental]/post-versions.mdx | 8 +- .../connections/delete-connections-by-id.mdx | 11 +- .../connections/get-connections-by-id.mdx | 11 +- .../endpoints/connections/get-connections.mdx | 8 +- .../post-connections-by-id-rotate.mdx | 11 +- .../connections/post-connections.mdx | 4 +- .../get-databases-by-database-id-backups.mdx | 11 +- .../get-databases-by-database-id-usage.mdx | 11 +- ...t-databases-by-database-id-connections.mdx | 11 +- ...t-databases-by-database-id-connections.mdx | 11 +- .../delete-databases-by-database-id.mdx | 11 +- .../get-databases-by-database-id.mdx | 11 +- .../endpoints/databases/get-databases.mdx | 4 +- .../get-projects-by-project-id-databases.mdx | 11 +- .../patch-databases-by-database-id.mdx | 11 +- ...atabases-by-target-database-id-restore.mdx | 13 +- .../endpoints/databases/post-databases.mdx | 4 +- .../post-projects-by-project-id-databases.mdx | 11 +- .../delete-integrations-by-id.mdx | 11 +- ...workspace-id-integrations-by-client-id.mdx | 11 +- .../integrations/get-integrations-by-id.mdx | 11 +- .../integrations/get-integrations.mdx | 4 +- ...orkspaces-by-workspace-id-integrations.mdx | 11 +- .../endpoints/misc/get-regions-accelerate.mdx | 7 +- .../endpoints/misc/get-regions-postgres.mdx | 7 +- .../projects/delete-projects-by-id.mdx | 11 +- .../endpoints/projects/get-projects-by-id.mdx | 8 +- .../endpoints/projects/get-projects.mdx | 4 +- .../projects/patch-projects-by-id.mdx | 11 +- .../projects/post-projects-by-id-transfer.mdx | 11 +- .../endpoints/projects/post-projects.mdx | 4 +- .../endpoints/regions/get-regions.mdx | 4 +- .../workspaces/get-workspaces-by-id.mdx | 11 +- .../endpoints/workspaces/get-workspaces.mdx | 4 +- .../content/docs/management-api/index.mdx | 4 +- .../content/docs/management-api/meta.json | 2 +- .../management-api/partner-integration.mdx | 2 +- apps/docs/content/docs/management-api/sdk.mdx | 7 +- .../docs/orm/core-concepts/api-patterns.mdx | 2 +- .../supported-databases/index.mdx | 14 +- .../supported-databases/meta.json | 10 +- .../supported-databases/mysql.mdx | 37 +- .../supported-databases/postgresql.mdx | 53 +- .../supported-databases/sql-server.mdx | 52 +- .../supported-databases/sqlite.mdx | 6 +- apps/docs/content/docs/orm/index.mdx | 4 +- .../content/docs/orm/more/best-practices.mdx | 189 +- .../more/comparisons/prisma-and-drizzle.mdx | 1 - .../more/comparisons/prisma-and-mongoose.mdx | 28 +- .../more/comparisons/prisma-and-sequelize.mdx | 70 +- .../docs/orm/more/dev-environment/meta.json | 5 +- apps/docs/content/docs/orm/more/releases.mdx | 4 +- .../many-to-many-relations.mdx | 2 +- .../docs/orm/more/troubleshooting/nextjs.mdx | 2 +- .../docs/orm/more/troubleshooting/nuxt.mdx | 2 +- .../troubleshooting/raw-sql-comparisons.mdx | 6 +- .../client-extensions/client.mdx | 7 +- .../client-extensions/extension-examples.mdx | 6 +- .../prisma-client/client-extensions/model.mdx | 6 +- .../prisma-client/client-extensions/query.mdx | 6 +- .../client-extensions/result.mdx | 6 +- .../shared-extensions/index.mdx | 5 +- .../shared-extensions/permit-rbac.mdx | 4 +- .../client-extensions/type-utilities.mdx | 6 +- .../debugging-and-troubleshooting/meta.json | 5 +- ...aveats-when-deploying-to-aws-platforms.mdx | 1 - ...oy-migrations-from-a-local-environment.mdx | 2 +- .../deployment/edge/deploy-to-cloudflare.mdx | 3 - .../deployment/edge/deploy-to-deno-deploy.mdx | 18 +- .../prisma-client/deployment/edge/meta.json | 7 +- .../deployment/edge/overview.mdx | 5 +- .../serverless/deploy-to-aws-lambda.mdx | 9 +- .../observability-and-logging/meta.json | 2 +- .../sql-comments.mdx | 4 +- .../query-optimization-performance.mdx | 9 +- .../aggregation-grouping-summarizing.mdx | 60 +- .../queries/relation-queries.mdx | 420 +- .../connection-management.mdx | 2 +- .../databases-connections/index.mdx | 4 +- .../databases-connections/pgbouncer.mdx | 4 +- .../setup-and-configuration/introduction.mdx | 11 +- ...ing-with-composite-ids-and-constraints.mdx | 4 +- .../working-with-geometry-fields.mdx | 31 +- .../working-with-json-fields.mdx | 4 +- .../working-with-scalar-lists-arrays.mdx | 4 +- .../orm/prisma-client/type-safety/index.mdx | 7 +- .../orm/prisma-client/type-safety/meta.json | 6 +- .../orm/prisma-client/using-raw-sql/index.mdx | 1 - .../using-raw-sql/raw-queries.mdx | 34 +- .../prisma-client/using-raw-sql/safeql.mdx | 4 +- .../orm/prisma-migrate/getting-started.mdx | 11 +- .../content/docs/orm/prisma-migrate/index.mdx | 3 +- .../content/docs/orm/prisma-migrate/meta.json | 7 +- .../limitations-and-known-issues.mdx | 2 +- .../mental-model.mdx | 2 +- .../migration-histories.mdx | 25 +- .../shadow-database.mdx | 5 - .../prisma-migrate/workflows/baselining.mdx | 18 +- .../workflows/development-and-production.mdx | 9 +- .../workflows/generating-down-migrations.mdx | 2 - .../workflows/patching-and-hotfixing.mdx | 1 - .../workflows/prototyping-your-schema.mdx | 353 +- .../workflows/squashing-migrations.mdx | 10 +- .../workflows/troubleshooting.mdx | 17 +- .../unsupported-database-features.mdx | 2 +- .../data-model/database-mapping.mdx | 1 - .../orm/prisma-schema/data-model/indexes.mdx | 3 - .../orm/prisma-schema/data-model/models.mdx | 4 +- .../data-model/relations/index.mdx | 4 +- .../relations/referential-actions.mdx | 5 +- .../data-model/relations/relation-mode.mdx | 1 - .../unsupported-database-features.mdx | 2 +- .../content/docs/orm/prisma-schema/meta.json | 7 +- .../prisma-schema/overview/data-sources.mdx | 1 - .../orm/prisma-schema/overview/generators.mdx | 1 - .../docs/orm/prisma-schema/overview/index.mdx | 3 - .../orm/prisma-schema/overview/location.mdx | 5 +- .../docs/orm/reference/connection-urls.mdx | 20 +- .../docs/orm/reference/error-reference.mdx | 6 +- .../docs/orm/reference/errors/index.mdx | 2 +- .../client-preview-features.mdx | 24 +- .../orm/reference/prisma-cli-reference.mdx | 38 +- .../orm/reference/prisma-client-reference.mdx | 140 +- .../orm/reference/prisma-schema-reference.mdx | 50 +- .../docs/postgres/database/backups.mdx | 2 + .../database/connecting-to-your-database.mdx | 14 +- .../postgres/database/connection-pooling.mdx | 10 +- .../postgres/database/local-development.mdx | 26 +- .../docs/postgres/database/query-insights.mdx | 2 +- .../postgres/database/serverless-driver.mdx | 2 +- .../content/docs/postgres/error-reference.mdx | 2 +- apps/docs/content/docs/postgres/faq.mdx | 5 +- .../docs/content/docs/postgres/iac/pulumi.mdx | 8 +- apps/docs/content/docs/postgres/index.mdx | 7 +- .../docs/postgres/integrations/raycast.mdx | 1 + .../content/docs/query-insights/index.mdx | 21 +- .../content/docs/query-insights/meta.json | 5 +- .../content/docs/studio/getting-started.mdx | 4 +- apps/docs/content/docs/studio/index.mdx | 2 +- apps/docs/cspell.json | 4 +- apps/docs/next.config.mjs | 4 +- apps/docs/scripts/add-url-frontmatter.ts | 7 +- apps/docs/scripts/fetch-openapi.ts | 11 +- apps/docs/scripts/generate-docs.ts | 28 +- apps/docs/scripts/lint-external-links.ts | 8 +- apps/docs/scripts/lint-links.ts | 41 +- apps/docs/source.config.ts | 12 +- apps/docs/src/app/(docs)/(default)/layout.tsx | 3 +- apps/docs/src/app/(docs)/sitemap.ts | 6 +- .../src/app/(docs)/v6/[[...slug]]/page.tsx | 21 +- apps/docs/src/app/(docs)/v6/layout.tsx | 55 +- apps/docs/src/app/api/github-webhook/route.ts | 18 +- apps/docs/src/app/api/newsletter/README.md | 8 +- apps/docs/src/app/api/newsletter/route.ts | 19 +- apps/docs/src/app/api/search/route.ts | 28 +- apps/docs/src/app/global-error.tsx | 6 +- apps/docs/src/app/global.css | 12 +- apps/docs/src/app/layout.tsx | 12 +- apps/docs/src/app/llms.txt/route.ts | 8 +- apps/docs/src/app/not-found.tsx | 5 +- apps/docs/src/app/og/[...slug]/route.tsx | 24 +- apps/docs/src/app/robots.ts | 30 +- apps/docs/src/app/rss.xml/route.ts | 4 +- apps/docs/src/components/ai-chat-sidebar.tsx | 116 +- .../components/ai-elements/conversation.tsx | 22 +- .../src/components/ai-elements/copy-chat.tsx | 2 +- apps/docs/src/components/ai-elements/index.ts | 34 +- .../src/components/ai-elements/message.tsx | 43 +- .../components/ai-elements/prompt-input.tsx | 30 +- .../src/components/ai-elements/shimmer.tsx | 25 +- .../src/components/ai-elements/spinner.tsx | 8 +- .../src/components/ai-elements/suggestion.tsx | 19 +- .../interactive-examples.tsx | 15 +- .../src/components/layout/language-toggle.tsx | 4 +- apps/docs/src/components/layout/link-item.tsx | 38 +- .../src/components/layout/notebook/client.tsx | 111 +- .../src/components/layout/notebook/index.tsx | 181 +- .../layout/notebook/page/client.tsx | 102 +- .../components/layout/notebook/page/index.tsx | 60 +- .../components/layout/notebook/sidebar.tsx | 77 +- .../src/components/layout/search-toggle.tsx | 24 +- apps/docs/src/components/layout/shared.tsx | 30 +- .../src/components/layout/sidebar/base.tsx | 78 +- .../components/layout/sidebar/link-item.tsx | 22 +- .../components/layout/sidebar/page-tree.tsx | 24 +- .../layout/sidebar/tabs/dropdown.tsx | 104 +- .../components/layout/sidebar/tabs/index.tsx | 18 +- .../src/components/layout/theme-toggle.tsx | 34 +- .../docs/src/components/not-found-tracker.tsx | 10 +- apps/docs/src/components/search.tsx | 39 +- .../src/components/sidebar-badge-provider.tsx | 2 +- apps/docs/src/components/sidebar-banner.tsx | 4 +- apps/docs/src/components/status-indicator.tsx | 4 +- apps/docs/src/components/structured-data.tsx | 70 +- apps/docs/src/components/toc/clerk.tsx | 30 +- apps/docs/src/components/toc/default.tsx | 26 +- apps/docs/src/components/toc/index.tsx | 22 +- .../src/components/toc/tracked-wrapper.tsx | 9 +- apps/docs/src/components/ui/button.tsx | 20 +- apps/docs/src/components/ui/collapsible.tsx | 10 +- apps/docs/src/components/ui/input-group.tsx | 74 +- apps/docs/src/components/ui/input.tsx | 4 +- apps/docs/src/components/ui/popover.tsx | 16 +- apps/docs/src/components/ui/scroll-area.tsx | 22 +- apps/docs/src/components/ui/textarea.tsx | 31 +- apps/docs/src/components/utm-persistence.tsx | 4 +- apps/docs/src/components/version-switcher.tsx | 35 +- apps/docs/src/hooks/use-ai-chat.ts | 5 +- apps/docs/src/hooks/use-chat-persistence.ts | 27 +- apps/docs/src/hooks/use-mobile.ts | 4 +- apps/docs/src/lib/get-llm-text.ts | 4 +- apps/docs/src/lib/merge-refs.ts | 4 +- apps/docs/src/lib/openapi.ts | 17 +- apps/docs/src/lib/rss.ts | 19 +- apps/docs/src/lib/source.ts | 9 +- apps/docs/src/lib/urls.ts | 20 +- apps/docs/src/mdx-components.tsx | 10 +- apps/docs/tsconfig.json | 26 +- apps/docs/vercel.json | 7391 ++++++++++++++--- apps/eclipse/cli.json | 2 +- .../design-system/components/accordion.mdx | 50 +- .../design-system/components/alert.mdx | 121 +- .../design-system/components/avatar.mdx | 100 +- .../design-system/components/badge.mdx | 12 +- .../design-system/components/banner.mdx | 13 +- .../design-system/components/breadcrumb.mdx | 11 +- .../design-system/components/button.mdx | 80 +- .../design-system/components/chart.mdx | 78 +- .../design-system/components/checkbox.mdx | 97 +- .../design-system/components/codeblock.mdx | 155 +- .../design-system/components/dialog.mdx | 92 +- .../design-system/components/dropdownmenu.mdx | 34 +- .../design-system/components/inlinetoc.mdx | 52 +- .../design-system/components/input.mdx | 60 +- .../design-system/components/label.mdx | 29 +- .../design-system/components/radio-group.mdx | 210 +- .../design-system/components/select.mdx | 77 +- .../design-system/components/slider.mdx | 35 +- .../design-system/components/spinner.mdx | 50 +- .../design-system/components/statistic.mdx | 43 +- .../design-system/components/steps.mdx | 173 +- .../design-system/components/switch.mdx | 83 +- .../design-system/components/table.mdx | 78 +- .../content/design-system/components/tabs.mdx | 124 +- .../design-system/components/textarea.mdx | 106 +- .../design-system/components/tooltip.mdx | 63 +- apps/eclipse/content/design-system/index.mdx | 16 +- apps/eclipse/content/design-system/meta.json | 9 +- .../content/design-system/tokens/colors.mdx | 69 +- .../content/design-system/tokens/meta.json | 8 +- .../content/design-system/tokens/spacing.mdx | 42 +- .../design-system/tokens/typography.mdx | 195 +- apps/eclipse/package.json | 2 +- apps/eclipse/postcss.config.mjs | 2 +- apps/eclipse/source.config.ts | 53 +- .../app/(design-system)/[[...slug]]/page.tsx | 24 +- .../src/app/(design-system)/layout.tsx | 19 +- .../src/app/(design-system)/sitemap.ts | 12 +- apps/eclipse/src/app/global.css | 12 +- apps/eclipse/src/app/layout.tsx | 13 +- .../src/components/chart-examples/README.md | 4 +- .../chart-examples/interactive-examples.tsx | 5 +- .../interactive-examples.tsx | 15 +- .../src/components/layout/language-toggle.tsx | 4 +- .../src/components/layout/link-item.tsx | 32 +- .../src/components/layout/notebook/client.tsx | 99 +- .../src/components/layout/notebook/index.tsx | 67 +- .../layout/notebook/page/client.tsx | 102 +- .../components/layout/notebook/page/index.tsx | 56 +- .../components/layout/notebook/sidebar.tsx | 77 +- .../src/components/layout/search-toggle.tsx | 24 +- apps/eclipse/src/components/layout/shared.tsx | 30 +- .../src/components/layout/sidebar/base.tsx | 78 +- .../components/layout/sidebar/link-item.tsx | 22 +- .../components/layout/sidebar/page-tree.tsx | 24 +- .../layout/sidebar/tabs/dropdown.tsx | 104 +- .../components/layout/sidebar/tabs/index.tsx | 18 +- .../src/components/layout/theme-toggle.tsx | 34 +- apps/eclipse/src/components/search.tsx | 7 +- .../switch-examples/interactive-examples.tsx | 26 +- apps/eclipse/src/components/toc/clerk.tsx | 30 +- apps/eclipse/src/components/toc/default.tsx | 26 +- apps/eclipse/src/components/toc/index.tsx | 22 +- apps/eclipse/src/components/ui/button.tsx | 20 +- .../eclipse/src/components/ui/collapsible.tsx | 10 +- apps/eclipse/src/components/ui/popover.tsx | 16 +- .../eclipse/src/components/ui/scroll-area.tsx | 22 +- apps/eclipse/src/lib/layout.shared.tsx | 26 +- apps/eclipse/src/lib/merge-refs.ts | 4 +- apps/eclipse/src/lib/source.ts | 14 +- apps/eclipse/src/lib/urls.ts | 2 +- apps/site/content/changelog/2024-05-15.mdx | 4 +- apps/site/content/changelog/2024-06-06.mdx | 4 +- apps/site/content/changelog/2024-09-26.mdx | 2 +- apps/site/content/changelog/2025-02-20.mdx | 8 +- apps/site/content/changelog/2025-03-13.mdx | 2 +- apps/site/content/changelog/2025-05-01.mdx | 10 +- apps/site/content/changelog/2025-05-19.mdx | 6 +- apps/site/content/changelog/2025-06-05.mdx | 6 +- apps/site/content/changelog/2025-06-17.mdx | 8 +- apps/site/content/changelog/2025-07-02.mdx | 10 +- apps/site/content/changelog/2025-07-17.mdx | 11 +- apps/site/content/changelog/2025-07-30.mdx | 17 +- apps/site/content/changelog/2025-08-13.mdx | 2 +- apps/site/content/changelog/2025-09-10.mdx | 4 +- apps/site/content/changelog/2025-10-08.mdx | 19 +- apps/site/content/changelog/2025-10-22.mdx | 6 +- apps/site/content/changelog/2025-11-19.mdx | 42 +- apps/site/content/changelog/2025-12-03.mdx | 49 +- apps/site/content/changelog/2026-01-21.mdx | 14 +- apps/site/content/changelog/2026-03-27.mdx | 24 +- apps/site/next.config.mjs | 46 +- apps/site/package.json | 6 +- apps/site/source.config.ts | 12 +- apps/site/src/app/(index)/page.tsx | 1 - apps/site/src/app/about/page.tsx | 90 +- apps/site/src/app/api/newsletter/route.ts | 19 +- apps/site/src/app/api/search/route.ts | 47 +- apps/site/src/app/careers/page.tsx | 42 +- .../site/src/app/changelog/[...slug]/page.tsx | 10 +- apps/site/src/app/changelog/layout.tsx | 6 +- apps/site/src/app/changelog/page.tsx | 24 +- apps/site/src/app/client/page.tsx | 16 +- apps/site/src/app/community/page.tsx | 35 +- apps/site/src/app/ecosystem/page.tsx | 19 +- apps/site/src/app/enterprise/page.tsx | 54 +- .../src/app/event-code-of-conduct/page.tsx | 9 +- apps/site/src/app/events/events-data.ts | 9 +- apps/site/src/app/events/page.tsx | 16 +- apps/site/src/app/global.css | 422 +- apps/site/src/app/layout.tsx | 10 +- apps/site/src/app/llms-content.ts | 7 +- .../app/mcp/_components/capability-cards.tsx | 15 +- .../src/app/mcp/_components/mcp-bubble.tsx | 17 +- .../app/mcp/_components/mcp-hero-section.tsx | 14 +- apps/site/src/app/mcp/page.tsx | 6 +- apps/site/src/app/migrate/page.tsx | 3 +- .../src/app/newsletter/newsletter-signup.tsx | 19 +- apps/site/src/app/newsletter/page.tsx | 19 +- apps/site/src/app/og/image.png/route.tsx | 11 +- apps/site/src/app/orm/page.tsx | 11 +- apps/site/src/app/partners/page.tsx | 106 +- apps/site/src/app/partners/tos/page.tsx | 5 +- apps/site/src/app/postgres/page.tsx | 7 +- .../src/app/pricing/pricing-calculator.tsx | 123 +- .../src/app/pricing/pricing-hero-plans.tsx | 39 +- apps/site/src/app/query-insights/page.tsx | 58 +- apps/site/src/app/robots.ts | 6 +- apps/site/src/app/showcase/page.tsx | 3 +- apps/site/src/app/stack/page.tsx | 10 +- apps/site/src/app/stack/stack-data.ts | 3 +- apps/site/src/app/studio/page.tsx | 4 +- apps/site/src/app/support-policy/page.tsx | 171 +- apps/site/src/app/support/page.tsx | 16 +- apps/site/src/app/terms/page.tsx | 3 +- apps/site/src/app/typedsql/page.tsx | 19 +- apps/site/src/components/careers/WorldMap.tsx | 12 +- .../components/careers/challenges.module.css | 680 +- .../components/careers/flexible.module.css | 340 +- apps/site/src/components/careers/masonry.tsx | 8 +- .../src/components/careers/open-roles.tsx | 54 +- .../components/careers/worldmap.module.css | 152 +- apps/site/src/components/client/api.tsx | 52 +- .../site/src/components/client/technology.tsx | 10 +- .../src/components/console-cta-button.tsx | 9 +- .../enterprise/footer-accordion.tsx | 21 +- apps/site/src/components/enterprise/form.tsx | 5 +- apps/site/src/components/glitch-particles.tsx | 7 +- .../src/components/homepage/antigravity.tsx | 45 +- apps/site/src/components/homepage/bento.tsx | 22 +- .../homepage/card-section/card-section.tsx | 11 +- .../homepage/card-section/logo-grid.tsx | 132 +- .../homepage/testimonials/MIGRATION.md | 53 +- .../homepage/testimonials/index.tsx | 9 +- .../testimonials/testimonial-item.tsx | 16 +- apps/site/src/components/legal-accordion.tsx | 8 +- apps/site/src/components/marquee.tsx | 6 +- .../site/src/components/migrate/hero-code.tsx | 16 +- .../src/components/navigation-wrapper.tsx | 11 +- apps/site/src/components/orm/info-stats.tsx | 4 +- apps/site/src/components/partners/form.tsx | 5 +- .../components/partners/partners-table.tsx | 45 +- apps/site/src/components/postgres.tsx | 8 +- .../site/src/components/prisma-with/README.md | 8 +- .../prisma-with/community-section.tsx | 10 +- apps/site/src/components/prisma-with/hero.tsx | 30 +- .../components/prisma-with/how-section.tsx | 8 +- .../prisma-with/resources-section.tsx | 5 +- .../components/prisma-with/why-section.tsx | 4 +- apps/site/src/components/query-insights.tsx | 30 +- apps/site/src/components/scroll-carousel.tsx | 42 +- .../src/components/showcase/post-card.tsx | 12 +- .../src/components/support/search-toggle.tsx | 22 +- apps/site/src/components/support/search.tsx | 7 +- .../src/components/support/support-card.tsx | 6 +- apps/site/src/components/technology.tsx | 10 +- apps/site/src/components/utm-persistence.tsx | 7 +- apps/site/src/data/event-code-of-conduct.tsx | 65 +- apps/site/src/data/partners-tos.tsx | 188 +- apps/site/src/data/privacy.tsx | 165 +- apps/site/src/data/showcase.ts | 9 +- apps/site/src/data/sla.tsx | 60 +- apps/site/src/data/terms.tsx | 632 +- apps/site/src/lib/changelog-source.tsx | 20 +- apps/site/src/lib/shiki_prisma.ts | 20 +- apps/site/src/lib/site-metadata.ts | 3 +- apps/site/src/lib/sitemap.ts | 29 +- apps/site/src/lib/url.ts | 26 +- apps/site/tailwind.config.ts | 4 +- packages/eclipse/ARCHITECTURE.md | 42 +- packages/eclipse/GETTING_STARTED.md | 62 +- packages/eclipse/TOKENS.md | 12 +- packages/eclipse/TROUBLESHOOTING.md | 57 +- packages/eclipse/package.json | 24 +- packages/eclipse/postcss.config.mjs | 2 +- packages/eclipse/src/components/accordion.tsx | 24 +- packages/eclipse/src/components/action.tsx | 230 +- packages/eclipse/src/components/alert.tsx | 17 +- packages/eclipse/src/components/avatar.tsx | 15 +- packages/eclipse/src/components/badge.tsx | 15 +- packages/eclipse/src/components/banner.tsx | 66 +- .../eclipse/src/components/breadcrumb.tsx | 85 +- packages/eclipse/src/components/button.tsx | 7 +- packages/eclipse/src/components/card.tsx | 106 +- packages/eclipse/src/components/chart.tsx | 60 +- packages/eclipse/src/components/checkbox.tsx | 4 +- packages/eclipse/src/components/codeblock.tsx | 60 +- packages/eclipse/src/components/dialog.tsx | 32 +- .../eclipse/src/components/dropdown-menu.tsx | 25 +- packages/eclipse/src/components/empty.tsx | 9 +- packages/eclipse/src/components/field.tsx | 49 +- packages/eclipse/src/components/files.tsx | 28 +- packages/eclipse/src/components/index.ts | 16 +- .../eclipse/src/components/inline-toc.tsx | 12 +- packages/eclipse/src/components/input.tsx | 10 +- packages/eclipse/src/components/label.tsx | 9 +- .../eclipse/src/components/radio-group.tsx | 8 +- packages/eclipse/src/components/select.tsx | 26 +- packages/eclipse/src/components/separator.tsx | 31 +- packages/eclipse/src/components/statistic.tsx | 8 +- packages/eclipse/src/components/steps.tsx | 9 +- packages/eclipse/src/components/table.tsx | 66 +- packages/eclipse/src/components/tabs.tsx | 16 +- packages/eclipse/src/components/textarea.tsx | 8 +- .../eclipse/src/components/type-table.tsx | 27 +- .../eclipse/src/components/ui/accordion.tsx | 5 +- packages/eclipse/src/components/ui/button.tsx | 20 +- .../eclipse/src/components/ui/codetabs.tsx | 18 +- packages/eclipse/src/components/ui/tabs.tsx | 12 +- packages/eclipse/src/index.ts | 2 +- packages/eclipse/src/lib/cn.ts | 2 +- packages/eclipse/src/lib/merge-refs.ts | 4 +- packages/eclipse/src/styles/fonts.css | 54 +- packages/eclipse/src/styles/globals.css | 1295 ++- packages/eclipse/src/styles/steps.css | 28 +- packages/ui/THEME.md | 46 +- packages/ui/src/components/aspect-ratio.tsx | 6 +- .../ui/src/components/author-avatar-group.tsx | 15 +- packages/ui/src/components/drawer.tsx | 63 +- packages/ui/src/components/dropdown-menu.tsx | 33 +- packages/ui/src/components/footer-badges.tsx | 46 +- packages/ui/src/components/footer.tsx | 53 +- .../ui/src/components/navigation-menu.tsx | 16 +- packages/ui/src/components/newsletter.tsx | 20 +- packages/ui/src/components/pdp-status.tsx | 17 +- packages/ui/src/components/post-card.tsx | 32 +- packages/ui/src/components/quote.tsx | 19 +- packages/ui/src/components/star-count.tsx | 4 +- packages/ui/src/components/theme-provider.tsx | 16 +- packages/ui/src/components/theme-toggle.tsx | 58 +- packages/ui/src/components/tooltip-info.tsx | 7 +- packages/ui/src/components/tooltip.tsx | 34 +- .../ui/src/components/utm-persistence.tsx | 37 +- packages/ui/src/components/web-navigation.tsx | 5 +- packages/ui/src/components/youtube-player.tsx | 4 +- packages/ui/src/data/footer.ts | 18 +- packages/ui/src/hooks/use-newsletter.ts | 7 +- packages/ui/src/lib/rss.ts | 8 +- packages/ui/src/lib/utm.ts | 3 +- packages/ui/src/styles/globals.css | 286 +- 1003 files changed, 24466 insertions(+), 20558 deletions(-) diff --git a/apps/blog/content/blog/about-mcp-servers-and-how-we-built-one-for-prisma/index.mdx b/apps/blog/content/blog/about-mcp-servers-and-how-we-built-one-for-prisma/index.mdx index 0c5674c2b1..18884f1f6a 100644 --- a/apps/blog/content/blog/about-mcp-servers-and-how-we-built-one-for-prisma/index.mdx +++ b/apps/blog/content/blog/about-mcp-servers-and-how-we-built-one-for-prisma/index.mdx @@ -32,7 +32,7 @@ A purely internet-trained LLM won't be able to help with these because it doesn' ### "Tool access" enables LLMs to interact with the outside world -In such scenarios, an LLM needs additional capabilities to interact with the "outside world"—to go beyond its knowledge of the web and perform actions on other systems. +In such scenarios, an LLM needs additional capabilities to interact with the "outside world"—to go beyond its knowledge of the web and perform actions on other systems. LLM providers have responded to this need by implementing so-called _tool_ access. Individual providers use different names for it: OpenAI calls it [function calling](https://platform.openai.com/docs/guides/function-calling?api-mode=responses), Anthropic refers to it as [tool use](https://docs.anthropic.com/en/docs/build-with-claude/tool-use/overview), and others use terms like "plugins" or "actions." @@ -42,8 +42,6 @@ This approach was messy because each LLM had a different interface for interacti For example, if you wanted multiple LLMs to access your file system, you would need to implement the same "file system access tool" multiple times, each tailored to a specific LLM. Here's what that might have looked like: - - ```ts // OpenAI SDK import { OpenAI } from "openai"; @@ -58,10 +56,11 @@ const response = await openai.chat.completions.create({ if (response.function_call?.name === "findInvoices") { const { year } = JSON.parse(response.function_call.arguments); const files = fs.readdirSync("/invoices"); - const matches = files.filter(f => f.includes(year)); - const contents = matches.map(f => fs.readFileSync(`/invoices/${f}`, "utf-8")); + const matches = files.filter((f) => f.includes(year)); + const contents = matches.map((f) => fs.readFileSync(`/invoices/${f}`, "utf-8")); } ``` + ```ts // Anthropic SDK import { Anthropic } from "@anthropic-ai/sdk"; @@ -77,12 +76,11 @@ const output = await anthropic.completions.create({ prompt }); if (output.includes("find_invoices")) { const year = extractYear(output); const files = fs.readdirSync("/invoices"); - const matches = files.filter(f => f.includes(year)); - const contents = matches.map(f => fs.readFileSync(`/invoices/${f}`, "utf-8")); + const matches = files.filter((f) => f.includes(year)); + const contents = matches.map((f) => fs.readFileSync(`/invoices/${f}`, "utf-8")); } ``` - With new LLMs popping up left and right, you can imagine how chaotic this will become if every LLM has its own, proprietary interface for accessing the outside world. ### Introducing MCP: Standardizing tool access for LLMs @@ -108,14 +106,15 @@ server.tool({ parameters: { year: "number" }, handler: ({ year }) => { const files = fs.readdirSync("/invoices"); - const matches = files.filter(f => f.includes(year)); - return matches.map(f => fs.readFileSync(`/invoices/${f}`, "utf-8")); + const matches = files.filter((f) => f.includes(year)); + return matches.map((f) => fs.readFileSync(`/invoices/${f}`, "utf-8")); }, }); const transport = new StdioServerTransport(); await server.connect(transport); ``` + Anthropic clearly struck a chord with this standard. If you were on X at the time, you probably saw multiple MCP posts a day. Google Trends for "MCP" tell the same story: ![](/about-mcp-servers-and-how-we-built-one-for-prisma/imgs/03cc8051d3cdfcabcb6037fcb5fbe1d33856fc99-1816x1400.png) @@ -134,11 +133,12 @@ All you need to augment an LLM with MCP server functionality is a CLI command to } } ``` + The AI tool runs the `command`, passes the `args`, and the LLM gains access to the server's tools. ## Building the Prisma MCP server -At Prisma, we've built the [most popular TypeScript ORM](https://www.prisma.io/blog/how-prisma-orm-became-the-most-downloaded-orm-for-node-js) and the [world's most efficient Postgres database](https://www.prisma.io/blog/announcing-prisma-postgres-early-access) running on unikernels. +At Prisma, we've built the [most popular TypeScript ORM](https://www.prisma.io/blog/how-prisma-orm-became-the-most-downloaded-orm-for-node-js) and the [world's most efficient Postgres database](https://www.prisma.io/blog/announcing-prisma-postgres-early-access) running on unikernels. Naturally, we wondered how we could use MCP to simplify database workflows for developers. @@ -180,43 +180,45 @@ The Prisma MCP server is quite simple and lightweight—[you can explore it on G ```shell prisma mcp ``` + Here's its basic structure: ```ts -import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js' -import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' -import type { PrismaConfigInternal } from '@prisma/config' -import { Command } from '@prisma/internals' -import execa from 'execa' +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import type { PrismaConfigInternal } from "@prisma/config"; +import { Command } from "@prisma/internals"; +import execa from "execa"; async function runCommand({ args, cwd }: { args: string[]; cwd: string }) { - const result = await execa.node(process.argv[1], args, { cwd }) - return `${result.stdout}\n${result.stderr}` + const result = await execa.node(process.argv[1], args, { cwd }); + return `${result.stdout}\n${result.stderr}`; } export class Mcp implements Command { public static new(): Mcp { - return new Mcp() + return new Mcp(); } public async parse(_argv: string[], _config: PrismaConfigInternal): Promise { const server = new McpServer({ - name: 'Prisma', + name: "Prisma", version, - }) + }); /** * The capabilities of the MCP are implemented here via * the `server.tool(...)` API. */ - const transport = new StdioServerTransport() - await server.connect(transport) + const transport = new StdioServerTransport(); + await server.connect(transport); - return '' + return ""; } } ``` + The `parse` function is being executed when the `prisma mcp --early-access` CLI command is invoked. It starts an MCP server that uses the `StdioServerTransport` (as opposed to `StreamableHTTPServerTransport`) because it’s running locally. What’s not shown in the above snippet is the actual implementation of the CLI commands, let’s zoom into the `parse` function and look at the `prisma migrate dev` and `prisma init --db` commands as examples: @@ -227,7 +229,7 @@ public async parse(_argv: string[], _config: PrismaConfigInternal): Promise **Launch Special until Nov 12, 2023** - As part of Prisma Accelerate’s GA launch, we're excited to celebrate with you and offer a **50% lifetime discount** on the base price of our Pro and Business plans when you subscribe by November 12, 2023. ## Prisma Accelerate in Production + To see what Prisma Accelerate can do, let's take a look at its implementation in two real-world cases: ### Cal.com: Enhancing scheduling efficiency @@ -55,7 +57,6 @@ Prisma Accelerate's robust connection pooling is designed to handle high volumes Prisma Accelerate seamlessly complements our platform, powering Cal.com to handle millions of database queries every day, across our global user base. - ### Formbricks: Privacy-first survey suite @@ -78,16 +79,15 @@ Leveraging Prisma Accelerate's connection pooling, Formbricks achieves seamless Thanks to Prisma Accelerate, we can seamlessly scale our serverless applications without concerns about data layer performance. - ## Prisma Accelerate and the Data DX Vision -With the rise in data complexity, developers encounter mounting challenges in managing, integrating, and deploying data-centric solutions. [Data DX](https://datadx.io) champions a simplified approach to data-driven application development, avoiding unnecessary complexity while retaining depth and detail. Prisma Accelerate aligns perfectly with this philosophy, serving as a pivotal tool in our Data DX journey. +With the rise in data complexity, developers encounter mounting challenges in managing, integrating, and deploying data-centric solutions. [Data DX](https://datadx.io) champions a simplified approach to data-driven application development, avoiding unnecessary complexity while retaining depth and detail. Prisma Accelerate aligns perfectly with this philosophy, serving as a pivotal tool in our Data DX journey. ### Time to get started We are incredibly excited about Prisma Accelerate and can't wait to see what you’ll build with it. As you dive in, please share your thoughts, engage with us on [Discord](https://pris.ly/discord), and let's build amazing data-driven applications together! -[Get started with Prisma Accelerate](https://pris.ly/accelerate-blog) \ No newline at end of file +[Get started with Prisma Accelerate](https://pris.ly/accelerate-blog) diff --git a/apps/blog/content/blog/accelerate-ipv6-first/index.mdx b/apps/blog/content/blog/accelerate-ipv6-first/index.mdx index aa1f039ab6..e4c5bf713a 100644 --- a/apps/blog/content/blog/accelerate-ipv6-first/index.mdx +++ b/apps/blog/content/blog/accelerate-ipv6-first/index.mdx @@ -57,9 +57,10 @@ function applyDNS64(ipv4: string): string { return `[64:ff9b::${octets[0]}${octets[1]}:${octets[2]}${octets[3]}]`; } ``` + ## IPv6-first -Prisma Accelerate takes advantage of NAT64 in an approach we call IPv6-first. Every region Prisma Accelerate supports operates an AWS NAT gateway with NAT64 enabled. EC2 instances are placed into an IPv6-only VPC subnet, which automatically provisions a ***public*** IPv6 address from a specific address range and ***no*** IPv4 address. When the EC2 instance attempts to connect to a user’s database, it will prefer a direct connection over the public IPv6 address if the database also advertises an IPv6 address. Otherwise, a DNS64 address will be resolved instead, routing the request through the attached NAT gateway and to the database over IPv4. All internal traffic, including the request from Cloudflare to AWS, is always IPv6. +Prisma Accelerate takes advantage of NAT64 in an approach we call IPv6-first. Every region Prisma Accelerate supports operates an AWS NAT gateway with NAT64 enabled. EC2 instances are placed into an IPv6-only VPC subnet, which automatically provisions a **_public_** IPv6 address from a specific address range and **_no_** IPv4 address. When the EC2 instance attempts to connect to a user’s database, it will prefer a direct connection over the public IPv6 address if the database also advertises an IPv6 address. Otherwise, a DNS64 address will be resolved instead, routing the request through the attached NAT gateway and to the database over IPv4. All internal traffic, including the request from Cloudflare to AWS, is always IPv6. Unfortunately, while this removes the IPv4 address cost, it does add additional data transfer costs over the NAT gateway. Prisma has decided to absorb this cost while the ecosystem adjusts to the new IPv6 landscape. We made this decision so that the transition is as seamless for our users as possible (you see now why we picked the title for this post? 😉). This decision is also in alignment with several of the developer experience focused principles that we’ve put across on our [Data DX manifesto](https://www.datadx.io/#manifesto). diff --git a/apps/blog/content/blog/accelerate-preview-release-ab229e69ed2/index.mdx b/apps/blog/content/blog/accelerate-preview-release-ab229e69ed2/index.mdx index 66c19e3b59..8b5c23ff40 100644 --- a/apps/blog/content/blog/accelerate-preview-release-ab229e69ed2/index.mdx +++ b/apps/blog/content/blog/accelerate-preview-release-ab229e69ed2/index.mdx @@ -23,8 +23,7 @@ tags: - [Boost your app performance and scalability](#boost-your-app-performance-and-scalability) - [Let us know what you think](#let-us-know-what-you-think) - -[Accelerate](https://www.prisma.io/data-platform/accelerate) has transitioned from Early Access to public Preview! Now you can leverage the power of a managed global cache and connection pool with Prisma with support for 300 locations globally and use a connection pool with support for 16 regions. +[Accelerate](https://www.prisma.io/data-platform/accelerate) has transitioned from Early Access to public Preview! Now you can leverage the power of a managed global cache and connection pool with Prisma with support for 300 locations globally and use a connection pool with support for 16 regions. ## Cache query results close to your servers @@ -46,8 +45,6 @@ Suppose a database is located in North America, and a request is made to the dat Accelerate extends your Prisma Client with an intuitive API offering granular control over established caching patterns on a per-query basis. Utilise [time-to-live](https://www.prisma.io/docs/data-platform/accelerate/concepts#time-to-live-ttl) (TTL) or [state-while-revalidate](https://www.prisma.io/docs/data-platform/accelerate/concepts#stale-while-revalidate-swr) (SWR) to fine-tune your cache behavior tailored to your application needs. - - ```javascript await prisma.user.findMany({ cacheStrategy: { @@ -55,6 +52,7 @@ await prisma.user.findMany({ }, }); ``` + ```javascript await prisma.user.findMany({ cacheStrategy: { @@ -62,6 +60,7 @@ await prisma.user.findMany({ }, }); ``` + ```javascript await prisma.user.findMany({ cacheStrategy: { @@ -71,7 +70,6 @@ await prisma.user.findMany({ }); ``` - > Learn more about caching strategies in our [docs](https://www.prisma.io/docs/data-platform/accelerate/concepts#cache-strategies). To comply with regulations regarding the storage of personally identifiable information (PII) like phone numbers, social security numbers, and credit card numbers, you may need to avoid caching query results. Excluding the `cacheStrategy` from your queries provides a straightforward way to opt out of caching your query results. @@ -82,7 +80,7 @@ To comply with regulations regarding the storage of personally identifiable info Accelerate seamlessly integrates with serverless and edge environments. While database connections are stateful, serverless and edge environments are stateless, making it challenging to manage stateful connections from a stateless environment. -In serverless environments, a sudden surge in traffic can spawn several ephemeral servers (also referred to as ‘serverless functions’) to handle requests. This results in each server opening up one or more database connections, eventually exceeding the database connection limit. +In serverless environments, a sudden surge in traffic can spawn several ephemeral servers (also referred to as ‘serverless functions’) to handle requests. This results in each server opening up one or more database connections, eventually exceeding the database connection limit. > Learn more about the serverless connection management challenge here. @@ -102,28 +100,24 @@ Accelerate caches query results with incredible efficiency, allowing for lightni For example, a query that usually takes 30 seconds to process can be cached with Accelerate, resulting in a response time of approximately 5-20 milliseconds, providing a massive ~1000x speedup. -| | Queries with Prisma Client | Queries with Prisma Client and Accelerate without caching | Queries with Prisma Client and Accelerate with caching | -| :-------------- | :-----------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------: | -| **Description** | Prisma Client connects directly to the database and executes the query | The query is routed through the connection pool instance hosted close to the database region | Accelerate caches the query | -| **Pros** | • No need to write SQL

• No additional service or component | • Built-in connection pooling for serverless and edge runtimes

• Allows Prisma to be used in Edge Functions

| • Built-in connection pooling for serverless and edge runtimes

• Allows Prisma to be used in Edge Functions

• Performance boost | -| **Cons** | • Setting up and managing connection pooling for serverless/edge environments

• Long round-trips in multi-region deployments | • Slight latency overhead due to routing query through Accelerate’s connection pooler | • Data may be stale | +| | Queries with Prisma Client | Queries with Prisma Client and Accelerate without caching | Queries with Prisma Client and Accelerate with caching | +| :-------------- | :-----------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------: | +| **Description** | Prisma Client connects directly to the database and executes the query | The query is routed through the connection pool instance hosted close to the database region | Accelerate caches the query | +| **Pros** | • No need to write SQL

• No additional service or component | • Built-in connection pooling for serverless and edge runtimes

• Allows Prisma to be used in Edge Functions

| • Built-in connection pooling for serverless and edge runtimes

• Allows Prisma to be used in Edge Functions

• Performance boost | +| **Cons** | • Setting up and managing connection pooling for serverless/edge environments

• Long round-trips in multi-region deployments | • Slight latency overhead due to routing query through Accelerate’s connection pooler | • Data may be stale | Accelerate enables you to serve more users with fewer resources. Storing frequently accessed query results reduces the need for repeated database queries. This improves scalability and performance by freeing up the database to perform more tasks. Application servers can handle more requests if query results are cached because the queries respond faster. This can help accelerate your website's response times. -
+
[Test Accelerate's speed](https://accelerate-speed-test.vercel.app/) -## Let us know what you think! - +## Let us know what you think! Get started to supercharge your application with Prisma Accelerate! Try it out and share your experience with us on Twitter or join the conversation on Discord. -
+ +
[Get started](https://pris.ly/pdp) -    +    [Read the docs](https://www.prisma.io/docs/data-platform/accelerate/what-is-accelerate) - - - - diff --git a/apps/blog/content/blog/accelerate-static-ip-support/index.mdx b/apps/blog/content/blog/accelerate-static-ip-support/index.mdx index 2cf79f15ad..1a966b7b06 100644 --- a/apps/blog/content/blog/accelerate-static-ip-support/index.mdx +++ b/apps/blog/content/blog/accelerate-static-ip-support/index.mdx @@ -30,17 +30,17 @@ Some of the security measures that can be considered include: By layering these defenses, you can better protect your database from various types of attacks and reduce the chances of a security breach. -To add one layer of these security measures, you can restrict database access from the public internet by configuring firewalls and IP allowlists to limit access to trusted IP addresses. It becomes possible if the IP addresses that require access to your database are static IP addresses. +To add one layer of these security measures, you can restrict database access from the public internet by configuring firewalls and IP allowlists to limit access to trusted IP addresses. It becomes possible if the IP addresses that require access to your database are static IP addresses. ![Access request to your database from unauthorized IP](/accelerate-static-ip-support/imgs/150247eeac00c5b575e06024aec82d5bf08eb57e-2960x1406.png) -A static IP address is an IPv4 or an IPv6 address that doesn’t change. Unlike dynamic IP addresses, which can change unpredictably, traffic from static IP addresses are easier to identify. +A static IP address is an IPv4 or an IPv6 address that doesn’t change. Unlike dynamic IP addresses, which can change unpredictably, traffic from static IP addresses are easier to identify. ![Dynamic vs Static IP](/accelerate-static-ip-support/imgs/14f0c87eaa0bb42137006d8f8b0ad16b23ac648b-2960x988.png) Using static IP addresses benefits managing and securing traffic by allowing you to configure your network firewalls or set up IP allowlisting to restrict access to your resources from specific IP addresses that don’t change. -Enabling static IP within your Prisma Accelerate configuration in a project environment allows using Accelerate with databases that have IP allowlisting enabled. +Enabling static IP within your Prisma Accelerate configuration in a project environment allows using Accelerate with databases that have IP allowlisting enabled. ![Allow Accelerate](/accelerate-static-ip-support/imgs/5fba5bb200c4418e360b6a4114e3a0d3c16d7fab-791x344.png) @@ -56,11 +56,11 @@ To enable static IP support for Accelerate within your existing or new project e Users can opt-in to using static IP in the Platform Console in two ways: -1. When enabling Accelerate for your project environment, opt-in to static IP after specifying your database connection string and connection pool region. - ![Enable static IP when enabling Accelerate](/accelerate-static-ip-support/imgs/970ae13572758d9962b6cc0b72b7e5a8c1af341b-2978x1976.png) +1. When enabling Accelerate for your project environment, opt-in to static IP after specifying your database connection string and connection pool region. + ![Enable static IP when enabling Accelerate](/accelerate-static-ip-support/imgs/970ae13572758d9962b6cc0b72b7e5a8c1af341b-2978x1976.png) 2. For projects already using Accelerate, enable static IP by navigating to Accelerate settings in your project environment. - ![Enable static IP when updating Accelerate configuration](/accelerate-static-ip-support/imgs/3be45327e6177e94ffb2739a1face51226c6ce5f-2572x1570.png) + ![Enable static IP when updating Accelerate configuration](/accelerate-static-ip-support/imgs/3be45327e6177e94ffb2739a1face51226c6ce5f-2572x1570.png) After you enable static IP within your Prisma Accelerate configuration, you’ll see a list of IPv4 and IPv6 addresses , which you will use to allow Accelerate traffic to your database. @@ -72,31 +72,30 @@ Configure your database firewall to limit incoming connections to trusted IP add Here's how to restrict incoming connections to your MongoDB Atlas-hosted database to originate only from Prisma Accelerate: -1. Enable static IP for Prisma Accelerate in your project environment. +1. Enable static IP for Prisma Accelerate in your project environment. 2. Copy the IPv4 addresses. - > ℹ️ MongoDB Atlas currently only allows you to add IPv4 addresses in the IP allow list. - > -3. Add the IPv4 addresses to your MongoDB Atlas network allow list: - > 📒 Visit the MongoDB [docs](https://www.mongodb.com/docs/atlas/security/ip-access-list/) for more info on managing IP access to your database. - > - 1. Go to your MongoDB Atlas project dashboard. - 2. In the **Security** section of the left navigation, click **Network Access**. The **IP Access List** tab displays. - 3. Click  ➕ **Add IP Address**. - 4. Then in the popup titled **Add IP Access List Entry,** keep adding the IPv4 static IP addresses you copied earlier in the **Access List Entry** field. Optionally, you can name the entry by adding a comment in the **Comment** field, for example, "Accelerate IP", to identify the static IP address later. - 5. Click **Confirm** to complete the step. - 6. Delete other IP addresses, such as an IP address entry `0.0.0.0/0`, which allows public access to your database by clicking on the **Delete** button in the **Actions** column. - -And you’re done! In a few steps, you’ve added a layer of security to your MongoDB database, ensuring Prisma Accelerate can access your database while unauthorized access requests are denied. + > ℹ️ MongoDB Atlas currently only allows you to add IPv4 addresses in the IP allow list. +3. Add the IPv4 addresses to your MongoDB Atlas network allow list: + > 📒 Visit the MongoDB [docs](https://www.mongodb.com/docs/atlas/security/ip-access-list/) for more info on managing IP access to your database. + 1. Go to your MongoDB Atlas project dashboard. + 2. In the **Security** section of the left navigation, click **Network Access**. The **IP Access List** tab displays. + 3. Click  ➕ **Add IP Address**. + 4. Then in the popup titled **Add IP Access List Entry,** keep adding the IPv4 static IP addresses you copied earlier in the **Access List Entry** field. Optionally, you can name the entry by adding a comment in the **Comment** field, for example, "Accelerate IP", to identify the static IP address later. + 5. Click **Confirm** to complete the step. + 6. Delete other IP addresses, such as an IP address entry `0.0.0.0/0`, which allows public access to your database by clicking on the **Delete** button in the **Actions** column. + +And you’re done! In a few steps, you’ve added a layer of security to your MongoDB database, ensuring Prisma Accelerate can access your database while unauthorized access requests are denied. ## Make your database more secure -Database security is increasingly important for data-driven apps, and adding an extra layer of protection to your database can reduce the risk of exposing sensitive data. +Database security is increasingly important for data-driven apps, and adding an extra layer of protection to your database can reduce the risk of exposing sensitive data. In just a few steps, you can increase the security of your database and make sure Accelerate can still access your database while access requests from unauthorized IPs are denied. If you need any help or have questions, reach out to us in our [Discord](https://pris.ly/discord) community. -
-[Go to Platform Console](https://console.prisma.io/login?utm_source=website&utm_medium=blog&utm_campaign=static-ip) +
+[Go to Platform +Console](https://console.prisma.io/login?utm_source=website&utm_medium=blog&utm_campaign=static-ip) Share with us your experience with Prisma Accelerate via a post in our [X](https://pris.ly/x). And to stay updated on new exciting feature releases, keep an eye on our [changelog](https://www.prisma.io/changelog). diff --git a/apps/blog/content/blog/advanced-database-schema-management-with-atlas-and-prisma-orm/index.mdx b/apps/blog/content/blog/advanced-database-schema-management-with-atlas-and-prisma-orm/index.mdx index 6ebca58e7f..b1614c02c6 100644 --- a/apps/blog/content/blog/advanced-database-schema-management-with-atlas-and-prisma-orm/index.mdx +++ b/apps/blog/content/blog/advanced-database-schema-management-with-atlas-and-prisma-orm/index.mdx @@ -17,9 +17,9 @@ tags: ## Introduction -[Atlas](https://atlasgo.io/) is a powerful data modeling and migrations tool that enables advanced database schema management workflows, like CI/CD integrations, schema monitoring, versioning, and more. +[Atlas](https://atlasgo.io/) is a powerful data modeling and migrations tool that enables advanced database schema management workflows, like CI/CD integrations, schema monitoring, versioning, and more. -In this guide, you will learn how to make use of Atlas advanced schema management and migration workflows by replacing Prisma Migrate in an existing Prisma ORM project with it. +In this guide, you will learn how to make use of Atlas advanced schema management and migration workflows by replacing Prisma Migrate in an existing Prisma ORM project with it. That way, you can still use Prisma ORM's intuitive data model and type-safe query capabilities while taking advantage of the enhanced migration capabilities provided by Atlas. @@ -44,7 +44,7 @@ To successfully complete this guide, you need: - a PostgreSQL database and its connection string - Docker installed on your machine (to manage Atlas' ephemeral dev databases) -For the purpose of this guide, we'll assume that your Prisma schema contains the standard `User` and `Post` models that we use as [main examples](](https://www.prisma.io/docs/orm/overview/introduction/what-is-prisma#the-prisma-schema)) across our documentation. If you don't have a Prisma ORM project, you can use the [`orm/script`](https://github.com/prisma/prisma-examples/tree/latest/orm/script) example to follow this guide. +For the purpose of this guide, we'll assume that your Prisma schema contains the standard `User` and `Post` models that we use as [main examples](<](https://www.prisma.io/docs/orm/overview/introduction/what-is-prisma#the-prisma-schema)>) across our documentation. If you don't have a Prisma ORM project, you can use the [`orm/script`](https://github.com/prisma/prisma-examples/tree/latest/orm/script) example to follow this guide. The starting point for this step is the [`start`](https://github.com/prisma/prisma-atlas/tree/start) branch in the example repo. @@ -55,6 +55,7 @@ To kick off this tutorial, first install the Atlas CLI: ```shell -sSf https://atlasgo.sh | sh ``` + If you prefer a different installation method (like Docker or Homebrew), you can find it [here](https://atlasgo.io/getting-started/#installation). Next, navigate into the root directory of your project that uses Prisma ORM and create the main [Atlas schema file](https://atlasgo.io/atlas-schema/hcl), called `atlas.hcl`: @@ -62,13 +63,14 @@ Next, navigate into the root directory of your project that uses Prisma ORM and ```shell touch atlas.hcl ``` + Now, add the following code it: ```hcl // atlas.hcl data "external_schema" "prisma" { - program = [ + program = [ "npx", "prisma", "migrate", @@ -91,15 +93,16 @@ env "local" { } } ``` + > To get syntax highlighting and other convenient features for the Atlas schema file, install the [Atlas VS Code extension](https://marketplace.visualstudio.com/items?itemName=Ariga.atlas-hcl). In the above snippet, you're doing two things: - Define an `external_schema` called `prisma` via the `data` block: Atlas is able to integrate database schema definitions from various sources. In this case, the _source_ is the SQL that's generated by the `prisma migrate diff` command which is specified via the `program` field. - Specify details about your environment (called `local`) using the `env` block: - - `dev`: Points to a [shadow database](https://www.prisma.io/docs/orm/prisma-migrate/understanding-prisma-migrate/shadow-database) (which is called _dev database_ in Atlas). Similar to Prisma Migrate, Atlas also uses a shadow database to "dry-run" migrations. The connection you provide here is similar to the `shadowDatabaseUrl` in the Prisma schema. However, for convenience we're using Docker in this case to manage these ephemeral database instances. - - `schema`: Points to the database connection URL of the database targeted by Prisma ORM (in most cases, this will be identical to the `DATABASE_URL` environment variable). - - `migration`: Points to the directory on your file system where you want to store the Atlas migration files (similar to the `prisma/migrations` folder). Note that you're also [excluding](https://atlasgo.io/versioned/diff#exclude-objects) the `_prisma_migrations` from being tracked in Atlas' migration history. + - `dev`: Points to a [shadow database](https://www.prisma.io/docs/orm/prisma-migrate/understanding-prisma-migrate/shadow-database) (which is called _dev database_ in Atlas). Similar to Prisma Migrate, Atlas also uses a shadow database to "dry-run" migrations. The connection you provide here is similar to the `shadowDatabaseUrl` in the Prisma schema. However, for convenience we're using Docker in this case to manage these ephemeral database instances. + - `schema`: Points to the database connection URL of the database targeted by Prisma ORM (in most cases, this will be identical to the `DATABASE_URL` environment variable). + - `migration`: Points to the directory on your file system where you want to store the Atlas migration files (similar to the `prisma/migrations` folder). Note that you're also [excluding](https://atlasgo.io/versioned/diff#exclude-objects) the `_prisma_migrations` from being tracked in Atlas' migration history. In addition to the shadow database, Atlas' migration system and Prisma Migrate have another commonality: They both use a dedicated table in the database to track the history of applied migrations. In Prisma Migrate, this table is called `_prisma_migrations`. In Atlas, it's called `atlas_schema_revisions`. @@ -110,12 +113,14 @@ To do that, first run the following command to create Atlas' migration directory ```shell atlas migrate diff --env local ``` + This command: 1. looks at the current state of your `local` environment and generates SQL migration files based on the `external_schema` defined in your Atlas schema. 2. creates the `atlas/migrations` folder and puts the SQL migration in there. After running it, your folder structure should look similar to this: + ``` . ├── README.md @@ -133,9 +138,10 @@ After running it, your folder structure should look similar to this: ├── src └── ... ``` -At this point, Atlas hasn't done anything to your database yet — it only created *files* on your local machine. -Now, you need to _apply_ the generated migrations to tell Atlas that this should be the beginning of its migration history. To do so, run the `atlas migrate apply` command but provide the `--baseline __TIMESTAMP__` option to it this time. +At this point, Atlas hasn't done anything to your database yet — it only created _files_ on your local machine. + +Now, you need to _apply_ the generated migrations to tell Atlas that this should be the beginning of its migration history. To do so, run the `atlas migrate apply` command but provide the `--baseline __TIMESTAMP__` option to it this time. Copy the timestamp from the filename that Atlas created inside `atlas/migrations` and use it to replace the `__TIMESTAMP__` placeholder value in the next snippet. Similarly, replace the `__DATABASE_URL__` placeholder with your database connection string: @@ -145,6 +151,7 @@ atlas migrate apply \ --url __DATABASE_URL__ \ --baseline __TIMESTAMP__ ``` + Assuming the generated migration file is called `20241210094213.sql` and your database is running at `postgresql://johndoe:mypassword42@localhost:5432/example-db?search_path=public&sslmode=disable`, the command should look as follows: ```shell @@ -153,11 +160,13 @@ atlas migrate apply \ --url "postgresql://johndoe:mypassword42@localhost:5432/example-db?search_path=public&sslmode=disable" \ --baseline 20241210094213 ``` + The command output will say the following: ```shell No migration files to execute ``` + If you inspect your database now, you'll see that the `atlas_schema_revisions` table has been created and contains two entries that specify the beginning of the Atlas migration history. > Your project should now be in a state looking similar to the [`step-1`](https://github.com/prisma/prisma-atlas/tree/step-1) branch of the example repo. @@ -167,7 +176,7 @@ If you inspect your database now, you'll see that the `atlas_schema_revisions` t Next, you'll learn how to make edits to your Prisma schema and reflect the change in your database using Atlas migrations. On a high-level, the process will look as follows: 1. Make a change to the Prisma schema -2. Run `atlas migrate diff` to create migration files +2. Run `atlas migrate diff` to create migration files 3. Run `atlas migrate apply` to execute the migration files against your database 4. Run `prisma generate` to update your Prisma Client 5. Access the modified schema in your application code via Prisma Client @@ -198,11 +207,13 @@ model Post { + posts Post[] + } ``` + With that change in place, now run the command to create the migration files on your machine: ```shell atlas migrate diff --env local ``` + As before, this creates a new file inside the `atlas/migrations` folder, e.g. `20241210132739.sql`, with the SQL code that reflects the change in your data model. In the case of our change above, it'll look like this: ```sql @@ -217,6 +228,7 @@ CREATE UNIQUE INDEX "_PostToTag_AB_unique" ON "_PostToTag" ("A", "B"); -- Create index "_PostToTag_B_index" to table: "_PostToTag" CREATE INDEX "_PostToTag_B_index" ON "_PostToTag" ("B"); ``` + Next, you can apply the migration with the same `atlas migrate apply` command as before, minus the `--baseline` option this time (remember to replace the `__DATABASE_URL__` placeholder): ```shell @@ -224,11 +236,13 @@ atlas migrate apply \ --env local \ --url __DATABASE_URL__ \ ``` + Your database schema is now updated, but your generated Prisma Client inside `node_modules/@prisma/client` isn't aware of the schema change yet. That's why you need to re-generate it using the Prisma CLI: ```shell npx prisma generate ``` + Now, you can go into your application code and run queries against the updated schema. In our case, that would be a query involving the new `Tag` model, e.g.: ```typescript @@ -236,11 +250,12 @@ const tag = await prisma.tag.create({ data: { name: "Technology", posts: { - create: { title: "Prisma and Atlas are a killer combo!" } - } - } -}) + create: { title: "Prisma and Atlas are a killer combo!" }, + }, + }, +}); ``` + > Your project should now be in a state looking similar to the [`step-2`](https://github.com/prisma/prisma-atlas/tree/step-2) branch of the example repo. ## Step 3: Add a partial index to the DB schema @@ -251,10 +266,10 @@ The workflow to achieve this looks as follows: 1. Create a SQL file inside the `atlas` directory that reflects the desired change 2. Update `atlas.hcl` to include that SQL file so that Atlas is aware of it -3. Run `atlas migrate diff` to create migration files +3. Run `atlas migrate diff` to create migration files 4. Run `atlas migrate apply` to execute the migration files against your database -This time, you won't need to re-generate Prisma Client because you didn't make any manual edits to the Prisma schema file. +This time, you won't need to re-generate Prisma Client because you didn't make any manual edits to the Prisma schema file. Let's go and add a partial index! @@ -263,13 +278,15 @@ First, create a file called `published_posts_index.sql` inside the `atlas` direc ```shell touch atlas/published_posts_index.sql ``` + Then, add the following code to it: ```sql CREATE INDEX "idx_published_posts" -ON "Post" ("id") +ON "Post" ("id") WHERE "published" = true; ``` + This creates an index on `Post` records that have their `published` field set to `true`. This query is useful when you query for these published posts, e.g.: ```typescript @@ -277,11 +294,12 @@ const publishedPosts = await prisma.post.findMany({ where: { published: true } } ``` + You now need to adjust the `atlas.hcl` file to make sure it's aware of the new SQL snippet for the schema. You can do this by using the [`composite_schema`](https://atlasgo.io/atlas-schema/projects#data-source-composite_schema) approach. Adjust your `atlas.hcl` file as follows: ```diff data "external_schema" "prisma" { - program = [ + program = [ "npx", "prisma", "migrate", @@ -314,6 +332,7 @@ env "local" { + } + } ``` + > Note that `composite_schema` is only available via the [Atlas Pro plan](https://atlasgo.io/features#pro) and requires you to be authenticated via `atlas login`. Atlas is now aware of the schema change, so you can go ahead and generate the migration files as before: @@ -321,6 +340,7 @@ Atlas is now aware of the schema change, so you can go ahead and generate the mi ```shell atlas migrate diff --env local ``` + You'll again see a new file inside the `atlas/migrations` directory. Go ahead and execute the migration with the same command as before (replacing `__DATABASE_URL__` with your own connection string): ```shell @@ -328,6 +348,7 @@ atlas migrate apply \ --env local \ --url __DATABASE_URL__ \ ``` + Congratulations! Your database is now updated with a partial index that will make your queries for published posts faster. > Your project should now be in a state looking similar to the [`step-3`](https://github.com/prisma/prisma-atlas/tree/step-3) branch of the example repo. diff --git a/apps/blog/content/blog/all-you-need-to-know-about-apollo-client-2-7e27e36d62fd/index.mdx b/apps/blog/content/blog/all-you-need-to-know-about-apollo-client-2-7e27e36d62fd/index.mdx index 0830b00d1c..c38d030453 100644 --- a/apps/blog/content/blog/all-you-need-to-know-about-apollo-client-2-7e27e36d62fd/index.mdx +++ b/apps/blog/content/blog/all-you-need-to-know-about-apollo-client-2-7e27e36d62fd/index.mdx @@ -11,7 +11,7 @@ heroImageAlt: "All you need to know about Apollo Client 2" --- Apollo Client, a powerful and flexible GraphQL client library, just reached version 2.0. In this post, we want to - highlight some of the major changes compared to the previous release. +highlight some of the major changes compared to the previous release. ## Simple modularity with Apollo Link @@ -28,26 +28,27 @@ Here is what a simple implementation for a Link that’s making HTTP calls based ```js class GraphQLRequestLink extends ApolloLink { constructor({ endpoint, headers }) { - super() - this.client = new GraphQLClient(endpoint, { headers }) + super(); + this.client = new GraphQLClient(endpoint, { headers }); } request(operation) { - return new Observable(observer => { - const { variables, query } = operation + return new Observable((observer) => { + const { variables, query } = operation; this.client .request(print(query), variables) - .then(data => { - observer.next(data) - observer.complete() + .then((data) => { + observer.next(data); + observer.complete(); }) - .catch(e => { - observer.error(e) - }) - }) + .catch((e) => { + observer.error(e); + }); + }); } } ``` + There are already a number of officially supported links which you can simply pull into your application using npm. Here’s a quick overview over a few of them (see [here](https://github.com/apollographql/apollo-link/tree/master/packages) for the full list): - [`apollo-link-http`](https://github.com/apollographql/apollo-link/tree/master/packages/apollo-link-http): Used to send GraphQL operations over HTTP @@ -74,29 +75,30 @@ This example from [Evans Hauser’s article](https://dev-blog.apollodata.com/apo ```js class CatchLink extends ApolloLink { request(operation, forward) { - const observable = forward(operation) + const observable = forward(operation); - return new Observable(observer => { + return new Observable((observer) => { const subscription = observable.subscribe({ next: observer.next.bind(observer), - error: error => { + error: (error) => { // reroute errors as proper data observer.next({ data: { error, }, - }) + }); }, complete: observer.complete.bind(observer), - }) + }); return () => { - subscription.unsubscribe() - } - }) + subscription.unsubscribe(); + }; + }); } } ``` + The `CatchLink` intercepts any errors that are received from the API and places them in the `data` field of the GraphQL response, thus treating them as regular response data which are not terminating the Observable. In the case of `next` and `completed` events it simply forwards these to the observers. The introduction of Observables opens the door to use links for implementing not only regular queries and mutations that follow the classic “request-response-cycle”, but also for subscriptions or live queries which continuously receive data from the server. @@ -106,36 +108,38 @@ The introduction of Observables opens the door to use links for implementing not If you have used `react-apollo` before, you most likely know that it was the only dependency you had to install in your application to import anything you’d need from Apollo Client (except for subscriptions). A typical setup with `react-apollo` looked as follows: ```js -import { ApolloProvider, createNetworkInterface, ApolloClient } from 'react-apollo' +import { ApolloProvider, createNetworkInterface, ApolloClient } from "react-apollo"; -const networkInterface = createNetworkInterface({ uri }) -const client = new ApolloClient({ networkInterface }) +const networkInterface = createNetworkInterface({ uri }); +const client = new ApolloClient({ networkInterface }); export default ( -) +); ``` + Since one major theme of Apollo Client 2.0 is _modularity_, you now have to import your functionality from multiple individual packages: ```js -import { ApolloProvider } from 'react-apollo' -import { ApolloClient } from 'apollo-client' -import { HttpLink } from 'apollo-link-http' -import { InMemoryCache } from 'apollo-cache-inmemory' +import { ApolloProvider } from "react-apollo"; +import { ApolloClient } from "apollo-client"; +import { HttpLink } from "apollo-link-http"; +import { InMemoryCache } from "apollo-cache-inmemory"; const client = new ApolloClient({ link: new HttpLink({ uri }), cache: new InMemoryCache(), -}) +}); export default ( -) +); ``` + In order to send queries and mutations, you also need to explicitly install the [`graphql-tag`](https://github.com/apollographql/graphql-tag) and even [`graphql`](https://github.com/graphql/graphql-js) libraries. To offer some convenience when getting started, the Apollo team created the [`apollo-client-presets`](https://www.npmjs.com/package/apollo-client-preset) package which includes `apollo-client`, `apollo-cache-inmemory` and `apollo-link-http`. Read more about the installation in the [README](https://github.com/apollographql/apollo-client#installation). @@ -145,4 +149,3 @@ To offer some convenience when getting started, the Apollo team created the [`ap Apollo Client is a community-driven effort and thanks to the new link concept, it’s possible to write dedicated pieces of functionality and share them with other developers. This enables different cache implementations (so you’re not depending on Redux any more when using Apollo Client), offline support, deferred queries and much more! Exciting times for GraphQL 💚 > To learn more about Apollo Client 2.0 follow the [React & Apollo tutorial on How to GraphQL](https://www.howtographql.com/react-apollo/0-introduction/). - diff --git a/apps/blog/content/blog/ambassador-program-nxkWGcGNuvFx/index.mdx b/apps/blog/content/blog/ambassador-program-nxkWGcGNuvFx/index.mdx index 7f5b07b157..e5656afbd1 100644 --- a/apps/blog/content/blog/ambassador-program-nxkWGcGNuvFx/index.mdx +++ b/apps/blog/content/blog/ambassador-program-nxkWGcGNuvFx/index.mdx @@ -18,9 +18,9 @@ We are thrilled to announce the [Prisma Ambassador Program](https://www.prisma.i ## A huge shoutout to the awesome Prisma community 💚 -We're continuosly amazed by the awesome content produced by the [Prisma community](https://www.prisma.io/community), be it in the form of example projects, video tutorials or blog articles! +We're continuosly amazed by the awesome content produced by the [Prisma community](https://www.prisma.io/community), be it in the form of example projects, video tutorials or blog articles! -Not to mention how community members support each other in [GitHub Discussions](https://github.com/prisma/prisma/discussions) and on [Stackoverflow](https://stackoverflow.com/questions/tagged/prisma) or have lively discussions and show off their Prisma projects on [Slack](https://slack.prisma.io) (join the [`#showcase`](https://app.slack.com/client/T0MQBS8JG/C565176N6) channel to learn more). +Not to mention how community members support each other in [GitHub Discussions](https://github.com/prisma/prisma/discussions) and on [Stackoverflow](https://stackoverflow.com/questions/tagged/prisma) or have lively discussions and show off their Prisma projects on [Slack](https://slack.prisma.io) (join the [`#showcase`](https://app.slack.com/client/T0MQBS8JG/C565176N6) channel to learn more). {/* */} @@ -28,7 +28,7 @@ Not to mention how community members support each other in [GitHub Discussions]( ## Recognising our community contributors -Community contributors have helped us immensely over the years by providing an unvarnished review of their Prisma experience, highlighting the good and the not so good. +Community contributors have helped us immensely over the years by providing an unvarnished review of their Prisma experience, highlighting the good and the not so good. This has helped shape the experience of new adopters as well as our own product and vision, allowing us to receive genuine and thoughtful feedback (e.g. via the [`#product-feedback`](https://app.slack.com/client/T0MQBS8JG/C01739JGFCM) channel where you can get in touch directly with our Product team). @@ -42,7 +42,7 @@ To make adoption easier and help our users be more successful with Prisma, we .. - ... incorporate feedback in our roadmap and release new versions [every two weeks](https://github.com/prisma/prisma) - ... invest heavily in education e.g. via extensive [documentation](https://www.prisma.io/docs) and [videos](https://pris.ly/youtube) -However, ultimately, **peer to peer feedback is indeed the most influencial approach there is**, and we want to reward those contributors who help spread the word about Prisma. +However, ultimately, **peer to peer feedback is indeed the most influencial approach there is**, and we want to reward those contributors who help spread the word about Prisma. --- @@ -94,4 +94,3 @@ New Ambassadors will be supported by a dedicated Prisma team throughout the prog Throughtout the years, the Prisma community has been a fantastic source of ideas, feedback and content. We couldn't be more grateful and proud to work for and with such amazing individuals! We hope the [Ambassador Program](https://www.prisma.io/ambassador) will enable you all to further shine and highlight the amazing work you are doing. Don't hesitate to reach out to learn more. We can't wait to see what further content the community will create! - diff --git a/apps/blog/content/blog/amplication-customer-story-nmlkBNlLlxnN/index.mdx b/apps/blog/content/blog/amplication-customer-story-nmlkBNlLlxnN/index.mdx index 9039de9bb9..0337c1745a 100644 --- a/apps/blog/content/blog/amplication-customer-story-nmlkBNlLlxnN/index.mdx +++ b/apps/blog/content/blog/amplication-customer-story-nmlkBNlLlxnN/index.mdx @@ -17,9 +17,9 @@ tags: ## Prioritizing developers' focus -Amplication enables development teams to focus their efforts on complex business logic and core functionality of their apps. Developers can then download the generated source code and start utilizing their skills to freely customize their project. +Amplication enables development teams to focus their efforts on complex business logic and core functionality of their apps. Developers can then download the generated source code and start utilizing their skills to freely customize their project. -With the help of Prisma, Amplication is packaging a full stack of modern tools for professional developers and driving the evolution of application development with low-code and open-source. +With the help of Prisma, Amplication is packaging a full stack of modern tools for professional developers and driving the evolution of application development with low-code and open-source. ## Empowering professional developers @@ -31,14 +31,14 @@ With Amplication, you can easily create data models and configure role-based acc For fullstack developers, their repetitive coding tasks are taken care of, but they still retain **complete ownership** of the code to deploy where they wish and are free to download the generated app code and continue development elsewhere. -Developers get the foundation of what they need to seamlessly start an app, and reserve the ability to alter and add the code they need with no lock-in. Amplication's offering is truly the best of both worlds. - +Developers get the foundation of what they need to seamlessly start an app, and reserve the ability to alter and add the code they need with no lock-in. Amplication's offering is truly the best of both worlds. ## The Amplication stack Amplication generates application code for you with the same building blocks they use themselves internally. The tools are all proven open-source and popular among the respective developer communities. For the server side you get: + - [NestJS](https://nestjs.com/): A progressive Node.js framework for building efficient, reliable and scalable server-side applications - [Prisma](https://www.prisma.io/): A next-generation ORM for Node.js and TypeScript - [PostgreSQL](https://www.postgresql.org/): The world’s most advanced open source relational database @@ -54,7 +54,7 @@ The Amplication team strongly believes in open-source technology and a user focu ## Betting on Prisma early -When first beginning work on Amplication in 2020, [Yuval Hazaz](https://twitter.com/Yuvalhazaz1), CEO at Amplication, made an early bet on Prisma to not just be a tool used by himself and his engineers, but also a central cog in the stack managed by Amplication users. Among other ORM options, Yuval felt Prisma was meeting developer needs the best and was strongly convinced by the Prisma community. Yuval was impressed by the consistent work done by the Prisma team to bring new features to its users based on feedback directly from the community. Amplication places a strong importance on the open-source community’s ability to collaborate and make better developer experiences, a sentiment shared at Prisma. +When first beginning work on Amplication in 2020, [Yuval Hazaz](https://twitter.com/Yuvalhazaz1), CEO at Amplication, made an early bet on Prisma to not just be a tool used by himself and his engineers, but also a central cog in the stack managed by Amplication users. Among other ORM options, Yuval felt Prisma was meeting developer needs the best and was strongly convinced by the Prisma community. Yuval was impressed by the consistent work done by the Prisma team to bring new features to its users based on feedback directly from the community. Amplication places a strong importance on the open-source community’s ability to collaborate and make better developer experiences, a sentiment shared at Prisma. -Aside from community, Prisma features also make life easier for the Amplication team. Prisma’s TypeScript experience was an important qualification for Amplication's data layer. Incorporating [NestJS](https://www.prisma.io/nestjs) with [GraphQL](https://www.prisma.io/graphql) in the Amplication-generated app made Prisma an easy choice in the stack. The [Prisma Client](https://www.prisma.io/client) integrates smoothly into the modular architecture of NestJS giving an incredible level of type-safety. +Aside from community, Prisma features also make life easier for the Amplication team. Prisma’s TypeScript experience was an important qualification for Amplication's data layer. Incorporating [NestJS](https://www.prisma.io/nestjs) with [GraphQL](https://www.prisma.io/graphql) in the Amplication-generated app made Prisma an easy choice in the stack. The [Prisma Client](https://www.prisma.io/client) integrates smoothly into the modular architecture of NestJS giving an incredible level of type-safety. -Yuval also knew that Prisma’s migrations were going to be critical for Amplication even in its infancy as a feature. +Yuval also knew that Prisma’s migrations were going to be critical for Amplication even in its infancy as a feature. -Yuval has seen [Prisma Migrate](https://www.prisma.io/migrate) improve since its first introduction, and it continues to deliver a quality developer experience. Prisma Migrate’s ability to automatically generate fully customizable database schema migrations from Prisma Schema changes keeps Amplication engineers and users focused on building out new app features rather than hassling with refactoring for entity changes and error-handling. +Yuval has seen [Prisma Migrate](https://www.prisma.io/migrate) improve since its first introduction, and it continues to deliver a quality developer experience. Prisma Migrate’s ability to automatically generate fully customizable database schema migrations from Prisma Schema changes keeps Amplication engineers and users focused on building out new app features rather than hassling with refactoring for entity changes and error-handling. Professional application development products rely on the ability to choose the right tools for their users. Amplication has a trust in the Prisma community and belief that Prisma features are delivering the best experience for developers. This is why they include it among other great tools in their generated app. diff --git a/apps/blog/content/blog/announcing-accelerate-usrvpi6sfkv4/index.mdx b/apps/blog/content/blog/announcing-accelerate-usrvpi6sfkv4/index.mdx index 939789a554..27e9ca11cc 100644 --- a/apps/blog/content/blog/announcing-accelerate-usrvpi6sfkv4/index.mdx +++ b/apps/blog/content/blog/announcing-accelerate-usrvpi6sfkv4/index.mdx @@ -34,5 +34,3 @@ We're so excited to have you join us on this journey, and we can't wait to hear
[Get on the waitlist](https://www.prisma.io/data-platform/accelerate) - - diff --git a/apps/blog/content/blog/announcing-discord-1LiAOpS7lxV9/index.mdx b/apps/blog/content/blog/announcing-discord-1LiAOpS7lxV9/index.mdx index 1db851102c..eacf3829c3 100644 --- a/apps/blog/content/blog/announcing-discord-1LiAOpS7lxV9/index.mdx +++ b/apps/blog/content/blog/announcing-discord-1LiAOpS7lxV9/index.mdx @@ -19,7 +19,7 @@ Hello Prisma Community, We're excited to share an important update with you: Prisma is making an entrance onto [Discord](https://discord.gg/KQyTW2H5ca)! While we take great pride in our strong community of over 50,000 members on Slack, we believe it's time to extend our reach and foster additional collaborative conversations on Discord, a platform many of you already enjoy and have asked us to be present on. We are happy to oblige! -You might already be aware of the unofficial Prisma community on Discord, a space that's been vibrant with discussions and brainstorming. We've seen the impressive engagement and support that this community has grown into and, after talks with the server's creator, we are excited to officially adopt and expand upon this community. +You might already be aware of the unofficial Prisma community on Discord, a space that's been vibrant with discussions and brainstorming. We've seen the impressive engagement and support that this community has grown into and, after talks with the server's creator, we are excited to officially adopt and expand upon this community. We would like to express our deepest gratitude to the original server owner, [Olyno](https://github.com/Olyno), whose hard work and dedication have provided a strong foundation for our new community presence on Discord. Their passion has created a dynamic space where Prisma users could connect, share ideas, and support each other. We are thrilled to be able to carry on their work. @@ -53,4 +53,3 @@ We're incredibly excited to embark on this new chapter on Discord and look forwa [Join Discord](https://discord.gg/KQyTW2H5ca) The Prisma Team - diff --git a/apps/blog/content/blog/announcing-on-demand-cache-invalidation-for-prisma-accelerate/index.mdx b/apps/blog/content/blog/announcing-on-demand-cache-invalidation-for-prisma-accelerate/index.mdx index 64a40088c5..c65fde3062 100644 --- a/apps/blog/content/blog/announcing-on-demand-cache-invalidation-for-prisma-accelerate/index.mdx +++ b/apps/blog/content/blog/announcing-on-demand-cache-invalidation-for-prisma-accelerate/index.mdx @@ -22,7 +22,6 @@ Caching stores frequently accessed data in a temporary layer for quicker access, > Explore our [speed test](https://accelerate-speed-test.prisma.io/) to experience firsthand how caching can dramatically improve your application's performance. > ![Prisma Accelerate speed test result GIF](/announcing-on-demand-cache-invalidation-for-prisma-accelerate/imgs/46cf59b4dcf666cc403923788d01a5eb0c959970-870x804.gif) -> **Benefits of caching:** @@ -38,9 +37,9 @@ However, keeping cached data accurate is key. On-demand cache invalidation, whic On-demand cache invalidation is crucial for maintaining data integrity while benefiting from the speed of having cached data. With earlier versions of Prisma Accelerate, depending on the cache strategy, you had to wait for TTL or SWR timers to expire, limiting control over data refresh timing. Now, with on-demand cache invalidation, you can refresh your cache exactly when needed, allowing for a more dynamic and responsive experience. -### Use-case: *Hackernews* forum +### Use-case: _Hackernews_ forum -Imagine a scenario with [*Hackernews*](https://news.ycombinator.com/), where new posts and upvotes are constantly being added. Caching can dramatically speed up fetching popular stories, reducing server load. However, without proper on-demand invalidation, users could be shown outdated rankings, comments, or even entirely removed posts. This delay can mislead users with outdated data, degrading the experience and lowering engagement. +Imagine a scenario with [_Hackernews_](https://news.ycombinator.com/), where new posts and upvotes are constantly being added. Caching can dramatically speed up fetching popular stories, reducing server load. However, without proper on-demand invalidation, users could be shown outdated rankings, comments, or even entirely removed posts. This delay can mislead users with outdated data, degrading the experience and lowering engagement. For example, a post gaining significant upvotes won’t reflect in real-time without on-demand invalidation, leaving the top posts list inaccurate. By employing this technique, updates like votes, comments, or edits are consistently reflected, keeping the feed fresh and users engaged. @@ -53,73 +52,70 @@ const { data, info } = await prisma.post .findMany({ take: 20, orderBy: { - createdAt: 'desc', + createdAt: "desc", }, cacheStrategy: { ttl: 120, }, }) - .withAccelerateInfo() - + .withAccelerateInfo(); ``` + Now, with Prisma Accelerate, you can invalidate the cache by using tags, which group cached query results for easier management. Let’s look at an example: 1. First, add a tag to the `cacheStrategy` of your query: - - ```ts - const { data, info } = await prisma.post - .findMany({ - take: 20, - orderBy: { - createdAt: 'desc', - }, - cacheStrategy: { - ttl: 600, - // add the tags option and label the cached query result - tags: ['posts'], - }, - }) - .withAccelerateInfo() - - ``` - + + ```ts + const { data, info } = await prisma.post + .findMany({ + take: 20, + orderBy: { + createdAt: "desc", + }, + cacheStrategy: { + ttl: 600, + // add the tags option and label the cached query result + tags: ["posts"], + }, + }) + .withAccelerateInfo(); + ``` + 2. Then, when adding a new post, use the `$accelerate.invalidate` to refresh the cache immediately with on-demand invalidation: - - ```ts - const newPost = await prisma.post.create({ - data: { - title: title, - content: text, - url: url, - vote: 0, - }, - }) - - await prisma.$accelerate.invalidate({ - tags: ['posts'], - }) - - ``` - + + ```ts + const newPost = await prisma.post.create({ + data: { + title: title, + content: text, + url: url, + vote: 0, + }, + }); + + await prisma.$accelerate.invalidate({ + tags: ["posts"], + }); + ``` + 3. Similarly, when you upvote a post, you can invalidate the cache as well: - - ```ts - await prisma.post.update({ - where: { - id: id, - }, - data: { - vote: { - increment: 1, - }, - }, - }) - - await prisma.$accelerate.invalidate({ - tags: ['posts'], - }) - - ``` + + ```ts + await prisma.post.update({ + where: { + id: id, + }, + data: { + vote: { + increment: 1, + }, + }, + }); + + await prisma.$accelerate.invalidate({ + tags: ["posts"], + }); + ``` And that’s how simple it is to achieve on-demand cache revalidation. Check out the [example app](https://pris.ly/cache-invalidation-acc-example) to see how it works. diff --git a/apps/blog/content/blog/announcing-prisma-2-n0v98rzc8br1/index.mdx b/apps/blog/content/blog/announcing-prisma-2-n0v98rzc8br1/index.mdx index f9182b0235..c4112001cc 100644 --- a/apps/blog/content/blog/announcing-prisma-2-n0v98rzc8br1/index.mdx +++ b/apps/blog/content/blog/announcing-prisma-2-n0v98rzc8br1/index.mdx @@ -30,7 +30,7 @@ The existing [landscape of database access libraries](https://www.prisma.io/data The jungle of existing tools and lack of best practices have two main consequences: - Developers aren't _productive_ because the existing tools don't fit their needs -- Developers aren't _confident_ that they're doing the "right thing" +- Developers aren't _confident_ that they're doing the "right thing" ## The solution: Prisma makes databases easy @@ -45,7 +45,7 @@ After running the Preview and Beta versions of Prisma 2.0 for almost a year and Prisma Client provides an entirely new way for developers to access a database with two main goals in mind: - **Boost productivity** by letting developers query data in natural and familiar ways -- **Increase confidence** with type-safety, auto-completion and a robust query API +- **Increase confidence** with type-safety, auto-completion and a robust query API ### How Prisma Client boosts productivity and raises confidence @@ -64,14 +64,12 @@ Here's an overview of the benefits you get from Prisma Client: One of the biggest benefits of Prisma Client is the level of abstraction it provides. It allows developers to think of their data in _objects_ (instead of SQL), reducing the cognitive and practical overhead of mapping _relational_ to _object-oriented_ data. -Although Prisma Client returns data as objects, [it's not an ORM](https://www.prisma.io/docs/concepts/overview/prisma-in-your-stack/is-prisma-an-orm) and therefore doesn't suffer from [common problems often caused by the object-relational impedance mismatch](http://blogs.tedneward.com/post/the-vietnam-of-computer-science/). +Although Prisma Client returns data as objects, [it's not an ORM](https://www.prisma.io/docs/concepts/overview/prisma-in-your-stack/is-prisma-an-orm) and therefore doesn't suffer from [common problems often caused by the object-relational impedance mismatch](http://blogs.tedneward.com/post/the-vietnam-of-computer-science/). Prisma doesn't map classes to tables and there are no complex model instances or hidden performance pitfalls (e.g. due to lazy loading) as often seen in traditional ORMs. Prisma Client provides a query API for _your_ database schema with a focus on _structural typing_ and natural querying (in that sense, it gets closest to the _data mapper_ pattern of traditional ORMs). As an example, assume you have these `User` and `Post` tables: - - ![Entity Relationship Diagram](/announcing-prisma-2-n0v98rzc8br1/imgs/cQRivY4.png) ```sql @@ -89,18 +87,16 @@ CREATE TABLE "Post" ( ); ``` - With Prisma Client, you can formulate queries like the following to read and write data in these tables: - - ```ts const postsByAuthor = await prisma.post.findMany({ - where: { - author: { id: 42 } + where: { + author: { id: 42 }, }, -}) +}); ``` + ```js [{ id: 1, @@ -113,75 +109,74 @@ const postsByAuthor = await prisma.post.findMany({ }] ``` - -As you can see, the resulting `postsByAuthor` contains an array of plain JavaScript objects (if you're using TypeScript, these objects will be strongly typed). +As you can see, the resulting `postsByAuthor` contains an array of plain JavaScript objects (if you're using TypeScript, these objects will be strongly typed). > **Note**: The `author` field is a _virtual_ [relation field](https://www.prisma.io/docs/concepts/components/prisma-schema/relations#relation-fields) that connects the `Post` to the `User` table in the Prisma Client API. Since it's not directly represented in the database, it can be [named in any way you like](https://www.prisma.io/docs/concepts/components/prisma-client/working-with-prismaclient/use-custom-model-and-field-names). You can also easily include the relations of a model, in this case, you could also retrieve the information about the "author" of the return posts: - - ```ts const postsByAuthorWithAuthorInfo = await prisma.post.findMany({ - where: { - author: { id: 42 } + where: { + author: { id: 42 }, }, include: { author: true, - } -}) + }, +}); ``` + ```js -[{ - id: 1, - title: "Follow Prisma on Twitter", - content: null, - authorId: 42, - author: { - id: 42, - email: "alice@prisma.io", - name: "Alice" - } -}, { - id: 2, - title: "Join us online for Prisma Day 2020", - content: null, - authorId: 42, - author: { - id: 42, - email: "alice@prisma.io", - name: "Alice" - } -}] +[ + { + id: 1, + title: "Follow Prisma on Twitter", + content: null, + authorId: 42, + author: { + id: 42, + email: "alice@prisma.io", + name: "Alice", + }, + }, + { + id: 2, + title: "Join us online for Prisma Day 2020", + content: null, + authorId: 42, + author: { + id: 42, + email: "alice@prisma.io", + name: "Alice", + }, + }, +]; ``` - Note again that the objects in `postsByAuthorWithAuthorInfo` are fully typed when using TypeScript, so accessing a non-existing property on the `author` of a post, in this case, would throw a compiler error. #### Working intuitively with relations Accessing related data (meaning, data from tables that are connected via _foreign keys_) can be especially tricky with existing database tools. This is mostly due to the fundamental mismatch of how these relations are represented in relational databases and object-oriented languages: -- **Relational**: Data is typically [_normalized_](https://en.wikipedia.org/wiki/Database_normalization) (flat) and uses _foreign keys_ to link across entities. The entities then need to be JOINed to manifest the actual relationships. +- **Relational**: Data is typically [_normalized_](https://en.wikipedia.org/wiki/Database_normalization) (flat) and uses _foreign keys_ to link across entities. The entities then need to be JOINed to manifest the actual relationships. - **Object-oriented**: Objects can be _deeply nested structures_ where you can traverse relationships simply by using [dot notation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors#Dot_notation). Prisma Client lets you intuitively read and write nested data: - - ```ts const result = await prisma.user.findMany({ - include: { + include: { posts: { select: { id: true, - title: true - } - } - } -}) + title: true, + }, + }, + }, +}); ``` + ```ts const result = await prisma.user.create({ data: { @@ -189,33 +184,32 @@ const result = await prisma.user.create({ email: "alice@prisma.io", posts: { create: { - title: "Hello World" - } - } - } -}) + title: "Hello World", + }, + }, + }, +}); ``` + ```ts -const result = await prisma.user - .findOne({ where: { id: 42 }}) - .posts() +const result = await prisma.user.findOne({ where: { id: 42 } }).posts(); ``` + ```ts const result = await prisma.user .findOne({ - where: { email: 'alice@prisma.io' }, + where: { email: "alice@prisma.io" }, }) .posts({ where: { title: { - startsWith: 'Hello', + startsWith: "Hello", }, }, - }) + }); ``` - -Note again that in all of the above cases, the `result` will be fully typed if you're using TypeScript! +Note again that in all of the above cases, the `result` will be fully typed if you're using TypeScript! #### A declarative and human-readable database schema @@ -223,8 +217,6 @@ Reading definitions of tables and other database structures using SQL (e.g. `CRE The Prisma schema is generated by introspecting your database and serves as the foundation for the query API of Prisma Client. As an example, this is the equivalent version of the [above](#thinking-in-objects-a-natural-and-familiar-query-api) `User` and `Post` definitions: - - ```prisma model User { id Int @default(autoincrement()) @id @@ -241,6 +233,7 @@ model Post { authorId Int } ``` + ```sql CREATE TABLE "User" ( id SERIAL PRIMARY KEY, @@ -256,7 +249,6 @@ CREATE TABLE "Post" ( ); ``` - > **Note**: We are also working on a tool for _database migrations_ called [Prisma Migrate](https://www.prisma.io/docs/concepts/components/prisma-migrate). With Prisma Migrate, the [introspection-based workflow](https://www.prisma.io/docs/concepts/overview/what-is-prisma#typical-prisma-workflows) is "reversed" and you can map your declarative Prisma schema to the database; Prisma Migrate will generate the required SQL statements and execute them against the database. #### Auto-completion for database queries @@ -275,77 +267,81 @@ Consider again the `User` and `Post` tables from the example before, Prisma gene ```ts type User = { - id: number - email: string - name: string | null -} + id: number; + email: string; + name: string | null; +}; type Post = { - id: number - authorId: number | null - title: string | null - content: string | null -} + id: number; + authorId: number | null; + title: string | null; + content: string | null; +}; ``` -Any plain [CRUD](https://www.prisma.io/docs/concepts/components/prisma-client/crud) query sent by Prisma Client will return a response of objects that are typed accordingly. However, consider again the query from above where `include` was used to fetch a relation: - +Any plain [CRUD](https://www.prisma.io/docs/concepts/components/prisma-client/crud) query sent by Prisma Client will return a response of objects that are typed accordingly. However, consider again the query from above where `include` was used to fetch a relation: ```ts const postsByAuthorWithAuthorInfo = await prisma.post.findMany({ - where: { - author: { id: 42 } + where: { + author: { id: 42 }, }, include: { author: true, - } -}) + }, +}); ``` + ```js -[{ - id: 1, - title: "Follow Prisma on Twitter", - content: null, - authorId: 42, - author: { - id: 42, - email: "alice@prisma.io", - name: "Alice" - } -}, { - id: 2, - title: "Join us online for Prisma Day 2020", - content: null, - authorId: 42, - author: { - id: 42, - email: "alice@prisma.io", - name: "Alice" - } -}] +[ + { + id: 1, + title: "Follow Prisma on Twitter", + content: null, + authorId: 42, + author: { + id: 42, + email: "alice@prisma.io", + name: "Alice", + }, + }, + { + id: 2, + title: "Join us online for Prisma Day 2020", + content: null, + authorId: 42, + author: { + id: 42, + email: "alice@prisma.io", + name: "Alice", + }, + }, +]; ``` - The objects inside `postsByAuthorWithAuthorInfo` don't match the generated `Post` type because they carry the additional `author` object. In that case, Prisma Client still provides full type-safety and is able to statically type the result! Here's what the type looks like: ```ts const postsByAuthorWithAuthorInfo: (Post & { - author: User | null; -})[] + author: User | null; +})[]; ``` + Thanks to this, the TypeScript compiler will catch cases where you're accessing properties that don't exist. For example, this would be illegal: ```ts // caught by the TypeScript compiler because `firstName` doesn't exist -postsByAuthorWithAuthorInfo[0].author.firstName +postsByAuthorWithAuthorInfo[0].author.firstName; ``` + #### A single source of truth for database and application models Database tools often have the problem of needing to _synchronize_ changes that are made to data models between _application code_ and the _database_. For example, after having changed a database table, developers often need to manually adjust the respective model in their application code and scan the codebase for usages of the table to update it. This makes database schema migrations and code refactorings scary because there's no guarantee the two layers remain in sync after the change! -Prisma Client takes a different approach to this problem. Instead of manually synchronizing changes between application code and database, Prisma Client's query API is _generated_ based on your database schema. +Prisma Client takes a different approach to this problem. Instead of manually synchronizing changes between application code and database, Prisma Client's query API is _generated_ based on your database schema. With that approach, you can simply re-generate Prisma Client after a database schema change and the changes will automatically be synchronized to your Prisma Client query API. Thanks to auto-completion and type-safety, updating your application code to the new queries will be a lot faster than with any other approach. @@ -386,30 +382,28 @@ Prisma Client can be used in traditional monolithic servers, microservice archit ## A strong ecosystem growing around Prisma -Despite Prisma's young age, we are very proud and excited about the emerging ecosystem and a variety of tools we see growing around it. +Despite Prisma's young age, we are very proud and excited about the emerging ecosystem and a variety of tools we see growing around it. -### The next generation of fullstack frameworks is based on Prisma +### The next generation of fullstack frameworks is based on Prisma The Node.js ecosystem is known for lots of different frameworks that try to streamline workflows and prescribe certain conventions. We are extremely humbled that many framework authors decide to use Prisma as their data layer of choice. The new [RedwoodJS](https://redwoodjs.com/) framework by GitHub co-founder [Tom Preston-Werner](https://twitter.com/mojombo) seeks to become the "Ruby on Rails" equivalent for Node.js. RedwoodJS is based on React and GraphQL and comes with a baked-in deployment model for serverless functions. -
- -
+
+ +
-Another framework with increasing anticipation and excitement in the community is [Blitz.js](http://blitzjs.com/). Blitz is build on top of Next.js and takes a fundamentally different approach compared to Redwood. Its goal is to completely eliminate the API server and ["bring back the simplicity of server rendered frameworks"](https://github.com/blitz-js/blitz/blob/canary/rfc-docs/01-architecture.md#introduction). +Another framework with increasing anticipation and excitement in the community is [Blitz.js](http://blitzjs.com/). Blitz is build on top of Next.js and takes a fundamentally different approach compared to Redwood. Its goal is to completely eliminate the API server and ["bring back the simplicity of server rendered frameworks"](https://github.com/blitz-js/blitz/blob/canary/rfc-docs/01-architecture.md#introduction). ### Build type safe GraphQL servers with Nexus and the Prisma plugin At Prisma, we're huge fans of GraphQL and believe in its bright future. That's why we founded the [Prisma Labs](https://github.com/prisma-labs/) team which dedicates its time to work on open source tools in the GraphQL ecosystem. -It is currently focused on building [Nexus](https://www.nexusjs.org/#/), a delightful application framework for developing GraphQL servers. As opposed to Redwood, Nexus is a _backend-only_ GraphQL framework and has no opinions on how you access the GraphQL API from the frontend. +It is currently focused on building [Nexus](https://www.nexusjs.org/#/), a delightful application framework for developing GraphQL servers. As opposed to Redwood, Nexus is a _backend-only_ GraphQL framework and has no opinions on how you access the GraphQL API from the frontend. Using the Prisma plugin for Nexus, you can expose Prisma models in your GraphQL API without the overhead of implementing the typical CRUD boilerplate that's needed when connecting GraphQL resolvers to a database. - - ```prisma model User { id Int @default(autoincrement()) @id @@ -426,51 +420,53 @@ model Post { authorId Int } ``` + ```ts -import { schema } from 'nexus' +import { schema } from "nexus"; schema.queryType({ definition(t) { - t.crud.user() + t.crud.user(); t.crud.users({ ordering: true, - }) - t.crud.post() + }); + t.crud.post(); t.crud.posts({ filtering: true, - }) + }); }, -}) +}); schema.mutationType({ definition(t) { - t.crud.createOneUser() - t.crud.createOnePost() - t.crud.deleteOneUser() - t.crud.deleteOnePost() + t.crud.createOneUser(); + t.crud.createOnePost(); + t.crud.deleteOneUser(); + t.crud.deleteOnePost(); }, -}) +}); schema.objectType({ - name: 'User', + name: "User", definition(t) { - t.model.id() - t.model.email() - t.model.name() - t.model.posts() + t.model.id(); + t.model.email(); + t.model.name(); + t.model.posts(); }, -}) +}); schema.objectType({ - name: 'Post', + name: "Post", definition(t) { - t.model.id() - t.model.title() - t.model.content() - t.model.author() + t.model.id(); + t.model.title(); + t.model.content(); + t.model.author(); }, -}) +}); ``` + ```graphql scalar DateTime @@ -486,18 +482,10 @@ input DateTimeFilter { } type Mutation { - createOnePost( - data: PostCreateInput! - ): Post! - createOneUser( - data: UserCreateInput! - ): User! - deleteOnePost( - where: PostWhereUniqueInput! - ): Post - deleteOneUser( - where: UserWhereUniqueInput! - ): User + createOnePost(data: PostCreateInput!): Post! + createOneUser(data: UserCreateInput!): User! + deleteOnePost(where: PostWhereUniqueInput!): Post + deleteOneUser(where: UserWhereUniqueInput!): User } enum OrderByArg { @@ -506,13 +494,7 @@ enum OrderByArg { } type Post { - author( - after: String - before: String - first: Int - last: Int - skip: Int - ): [User!]! + author(after: String, before: String, first: Int, last: Int, skip: Int): [User!]! id: ID! } @@ -549,9 +531,7 @@ input PostWhereUniqueInput { } type Query { - post( - where: PostWhereUniqueInput! - ): Post + post(where: PostWhereUniqueInput!): Post posts( after: String before: String @@ -560,9 +540,7 @@ type Query { skip: Int where: PostWhereInput ): [Post!]! - user( - where: UserWhereUniqueInput! - ): User + user(where: UserWhereUniqueInput!): User users( after: String before: String @@ -591,13 +569,7 @@ type User { birthDate: DateTime! email: String! id: ID! - posts( - after: String - before: String - first: Int - last: Int - skip: Int - ): [Post!]! + posts(after: String, before: String, first: Int, last: Int, skip: Int): [Post!]! } input UserCreateInput { @@ -646,8 +618,7 @@ input UserWhereUniqueInput { } ``` - -Thanks to that plugin, there's almost no boilerplate needed to expose full CRUD operations, including filters, pagination, and ordering capabilities, for Prisma models. +Thanks to that plugin, there's almost no boilerplate needed to expose full CRUD operations, including filters, pagination, and ordering capabilities, for Prisma models. --- @@ -678,15 +649,14 @@ A few highlights include: - an [egghead video series](https://egghead.io/playlists/get-started-with-prisma-v2-prisma-client-8bae) by [Dimitri Ivashchuk](https://twitter.com/DivDev_) - a [tutorial](https://leerob.io/blog/next-prisma) and [video](https://www.youtube.com/watch?v=wk-dUsLUADg) about using Prisma with Next.js by [Lee Robinson](https://twitter.com/leeerob) - a [Nest.js starter kit](https://github.com/fivethree-team/nestjs-prisma-starter) by [Marc Julian](https://twitter.com/mrcjln) and [friends](https://github.com/fivethree-team/nestjs-prisma-starter/graphs/contributors) - - + + ### Join us at Prisma Day on June 25th and 26th for workshops and talks After the successful premiere last year, we are excited to host another edition of [Prisma Day](https://www.prisma.io/day) on June 25th (workshops) and 26th (talks). -This year, we are going remote and are inviting everyone to join us for amazing talks around modern application development, best practices for database workflows and everything Prisma! +This year, we are going remote and are inviting everyone to join us for amazing talks around modern application development, best practices for database workflows and everything Prisma!
{/* */} - diff --git a/apps/blog/content/blog/announcing-prisma-2-zq1s745db8i5/index.mdx b/apps/blog/content/blog/announcing-prisma-2-zq1s745db8i5/index.mdx index 61e6047ef9..5d64aba551 100644 --- a/apps/blog/content/blog/announcing-prisma-2-zq1s745db8i5/index.mdx +++ b/apps/blog/content/blog/announcing-prisma-2-zq1s745db8i5/index.mdx @@ -103,16 +103,14 @@ The data model is at the heart of your Photon and Lift workflows. It serves as a You can install the [Prisma 2 CLI](https://github.com/prisma/prisma2/blob/master/docs/prisma-2-cli.md) using npm or Yarn: - - ```shell npm install -g prisma2 ``` + ```shell yarn global add prisma2 ``` - ### 2. Run the interactive `prisma2 init` flow & select boilerplate Run the following command to get started: @@ -120,6 +118,7 @@ Run the following command to get started: ```shell prisma2 init hello-prisma2 ``` + Select the following in the interactive prompts: 1. Select **SQLite** @@ -135,6 +134,7 @@ Move into the `hello-prisma2` directory and install the Node dependencies. cd hello-prisma2 npm install ``` + ### 3. Migrate your database with Lift Migrating your database with Lift follows a 2-step process: @@ -148,6 +148,7 @@ In CLI commands, these steps can be performed as follows (_the CLI steps are in prisma2 lift save --name 'init' prisma2 lift up ``` + ### 4. Access your database with Photon The script in `src/script.ts` contains some sample API calls, e.g.: @@ -175,16 +176,19 @@ const postsByUser = await photon.users .posts() } ``` + You can seed your database using the seed script from `package.json`: ```shell npm run seed ``` + You can execute the script with the following command: ```shell npm run start ``` + ### 5. Build an app With Photon being connected to your database, you can now start building your application. In the [`photonjs`](https://github.com/prisma/photonjs/) repository, you can find reference examples for the following use cases (for JavaScript and TypeScript): @@ -192,8 +196,8 @@ With Photon being connected to your database, you can now start building your ap - [GraphQL example](https://github.com/prisma/photonjs/tree/master/examples/typescript/graphql) - [REST example](https://github.com/prisma/photonjs/tree/master/examples/typescript/rest-express) - [gRPC example](https://github.com/prisma/photonjs/tree/master/examples/typescript/grpc) - - + + --- @@ -221,64 +225,69 @@ While most traditional ORMs try to simply abstract SQL into a programming langua Especially when working with _relations_, Photon's API is a lot more developer-friendly compared to traditional ORMs. JOINs and atomic transactions are abstracted elegantly into nested API calls. Here are a few examples: - - ```ts // Retrieve the posts of a user -const postsByUser: Post[] = await photon.users.findOne({ where: { email: 'ada@prisma.io' } }).posts() +const postsByUser: Post[] = await photon.users + .findOne({ where: { email: "ada@prisma.io" } }) + .posts(); // Retrieve the categories of a post -const categoriesOfPost: Category[] = await photon.posts.findOne({ where: { id: 1 } }).categories() +const categoriesOfPost: Category[] = await photon.posts.findOne({ where: { id: 1 } }).categories(); ``` + ```ts // The returned post objects will only have the `id` and // `author` property which carries the respective user object const allPosts: Post[] = await photon.posts.findMany({ select: { id: true, author: true }, -}) +}); // The returned posts objects will have all scalar fields of the `Post` model and additionally all the categories for each post const allPosts: Post[] = await photon.posts.findMany({ include: { categories: true }, -}) +}); ``` + ```ts // Retrieve all posts of a particular user // that start with "Hello" const posts: Post[] = await photon.users .findOne({ - where: { email: 'ada@prisma.io' }, + where: { email: "ada@prisma.io" }, }) .posts({ where: { - title: { startsWith: 'Hello' }, + title: { startsWith: "Hello" }, }, - }) + }); ``` + ```ts // Create a new user with two posts in a // single transaction const newUser: User = await photon.users.create({ data: { - email: 'alice@prisma.io', + email: "alice@prisma.io", posts: { - create: [{ title: 'Join the Prisma Slack on https://slack.prisma.io' }, { title: 'Follow @prisma on Twitter' }], + create: [ + { title: "Join the Prisma Slack on https://slack.prisma.io" }, + { title: "Follow @prisma on Twitter" }, + ], }, }, -}) +}); // Change the author of a post in a single transaction const updatedPost: Post = await photon.posts.update({ where: { id: 5424 }, data: { author: { - connect: { email: 'alice@prisma.io' }, + connect: { email: "alice@prisma.io" }, }, }, -}) +}); ``` - Learn more about Photon's relations API in the [docs](https://github.com/prisma/prisma2/blob/master/docs/relations.md#relations-in-the-generated-Photon-API). ### Safe & resilient migrations for simple and complex use cases with Lift @@ -291,6 +300,7 @@ In the vast majority of cases, developers can simple adjust their declarative [d prisma2 lift save # stores a new migration folder on the file system prisma2 lift up # applies the migration from the previous step ``` + Whenever this workflow doesn't match your needs, you can extend it using "before/after"-hooks to run custom code before or after the migration is performed. The migration folders (and the migrations history table in the database) let developers further do easy rollbacks of migration. @@ -337,6 +347,7 @@ model Post { author User } ``` + Prisma 2 also comes with a [VS Code extension](https://marketplace.visualstudio.com/items?itemName=Prisma.prisma) that provides **auto-formatting** and **syntax highlighting** (and more features like auto-completion, jump-to-definition and linting coming soon) for the data modeling syntax! Learn more about the improved data modeling syntax in the [docs](https://github.com/prisma/prisma2/blob/master/docs/data-modeling.md). @@ -351,32 +362,30 @@ Photon provides a powerful data access API with some slight changes and improvem The CRUD operations are _unified_ across models and are accessible via a property on your Photon instance, e.g. for the model `User` you can access the operations to read and write data as follows: - - ```ts const newUser = await photon.users.create({ data: { - name: 'Alice', + name: "Alice", }, -}) +}); -const allUsers = await photon.users.findMany() +const allUsers = await photon.users.findMany(); const oneUser = await photon.users.findOne({ where: { id: 1 }, -}) +}); ``` + ```ts const newUser = await prisma.createUser({ - name: 'Alice', -}) + name: "Alice", +}); -const allUsers = await prisma.users()() +const allUsers = await prisma.users()(); const oneUser = await prisma.user({ id: 1, -}) +}); ``` - Note that the name of the `users` property is generated using the [`pluralize`](https://github.com/blakeembrey/pluralize) package. You can find the API reference for Photon.js in the [docs](https://github.com/prisma/prisma2/blob/master/docs/photon/api.md). #### Type-safe field selection via `select` and `include` @@ -394,23 +403,24 @@ Assume you your Photon API was generated from the data model [above](#improved-d // Default selection const oneUser = await photon.users.findOne({ where: { id: 1 }, -}) +}); // oneUser = { id: 1, name: "Alice", email: "alice@prisma.io" } // Select exclusively const oneUser = await photon.users.findOne({ where: { id: 1 }, select: { email: true }, -}) +}); // oneUser = { email: "alice@prisma.io" } // Include additionally const oneUser = await photon.users.findOne({ where: { id: 1 }, include: { posts: true }, -}) +}); // oneUser = { id: 1, name: "Alice", email: "alice@prisma.io", posts: [ ... ] } ``` + This code snippet only highlights `select` and `include` for `findOne`, but you can provide these options to any other CRUD operation: `findMany`, `create`, `update` and `delete`. You can learn more about the field selection API of Photon.js in the [docs](https://github.com/prisma/prisma2/blob/master/docs/photon/api.md#field-selection). @@ -459,4 +469,3 @@ The Prisma 2 Preview is not the only exciting thing happening this week. We are - [GraphQL Conf](https://www.graphqlconf.org/) (**Late bird tickets still available**) We are especially looking forward to welcoming the Prisma community at Prisma Day for a day of inspiring talks and great conversations. See you all tomorrow! 🙌 - diff --git a/apps/blog/content/blog/announcing-prisma-6-18-0/index.mdx b/apps/blog/content/blog/announcing-prisma-6-18-0/index.mdx index c27e4dc5ac..adb3a6689b 100644 --- a/apps/blog/content/blog/announcing-prisma-6-18-0/index.mdx +++ b/apps/blog/content/blog/announcing-prisma-6-18-0/index.mdx @@ -18,7 +18,7 @@ We just released Prisma [6.18.0](https://github.com/prisma/prisma/releases/tag/6 ## In-depth Database Metrics -Last release, we included a new workspace metrics overview, so folks can get a quick glance on their usage data across your entire workspace. Now, we’ve added more insights into your database usage with database metrics. +Last release, we included a new workspace metrics overview, so folks can get a quick glance on their usage data across your entire workspace. Now, we’ve added more insights into your database usage with database metrics. ![](/announcing-prisma-6-18-0/imgs/9393decb8beaf4ed3d4df4be8ab04809a83dcdfc-2880x1664.png) @@ -38,7 +38,7 @@ As we prepare for Prisma v7, we’re moving more functionality into the `prisma. ## Define your datasource in `prisma.config.ts` -If you’re adopting the new `prisma.config.ts` setup in your projects, version 6.18.0 brings the ability to set your datasource directly in your config file. Once this is in your config file, any datasource set in your `schema.prisma` will be ignored. To set the datasource, we also must include the new `engine` key which we can set to `"classic"`, which will be required for Prisma v7 +If you’re adopting the new `prisma.config.ts` setup in your projects, version 6.18.0 brings the ability to set your datasource directly in your config file. Once this is in your config file, any datasource set in your `schema.prisma` will be ignored. To set the datasource, we also must include the new `engine` key which we can set to `"classic"`, which will be required for Prisma v7 ```typescript import { defineConfig, env } from "prisma/config"; @@ -50,6 +50,7 @@ export default defineConfig({ }, }); ``` + ## Migrating to `prisma.config.ts` A good amount of work we’ve done in 6.18.0 has been to get the Prisma Config to match functionality to the Schema file. For with this release, we’d like to encourage developer to begin migrating to the Prisma Config. This way we can gather more feedback before we hit our next major release and can address any gaps in functionality. As part of this, we’ve included a small migration guide below for developers to follow. @@ -69,25 +70,28 @@ generator client { - url = env("DATABASE_URL") - } ``` + 2. Add any drivers you may need for your database - + If you’re using, for example PostgresSql, you’ll need to add the necessary driver. In this case `@prisma/adapter-pg` can be used. If you’re using Prisma Postgres, then there’s no extra steps needed here. - + ```shell npm install @prisma/adapter-pg ``` + Then in your `PrismaClient` setup, add the adapter as an optional parameter. - + ```ts -import { PrismaClient } from './generated/prisma' -import { PrismaPg } from '@prisma/adapter-pg' +import { PrismaClient } from "./generated/prisma"; +import { PrismaPg } from "@prisma/adapter-pg"; -const adapter = new PrismaPg({ connectionString: env.DATABASE_URL }) -const prisma = new PrismaClient({ adapter }) +const adapter = new PrismaPg({ connectionString: env.DATABASE_URL }); +const prisma = new PrismaClient({ adapter }); ``` -3. Migrate to `prisma.config.ts` -The `prisma.config.ts` is the new home for configuring your Prisma project. +3. Migrate to `prisma.config.ts` + +The `prisma.config.ts` is the new home for configuring your Prisma project. ```tsx import path from "node:path"; @@ -100,10 +104,11 @@ export default defineConfig({ }, engine: "classic", datasource: { - url: env('DATABASE_URL'), - } + url: env("DATABASE_URL"), + }, }); ``` + For a full reference of the options that can be used in Prisma Config, see the [docs](https://www.prisma.io/docs/orm/reference/prisma-config-reference). Be sure to follow us on social media to stay up to date with all the latest release of Prisma ORM and Prisma Postgres diff --git a/apps/blog/content/blog/announcing-prisma-6-19-0/index.mdx b/apps/blog/content/blog/announcing-prisma-6-19-0/index.mdx index 1177210de7..fa63343ade 100644 --- a/apps/blog/content/blog/announcing-prisma-6-19-0/index.mdx +++ b/apps/blog/content/blog/announcing-prisma-6-19-0/index.mdx @@ -19,14 +19,16 @@ We just released Prisma 6.19.0 ORM, which includes several bug fixes and improve ## How to enable connection pooling with Prisma Postgres We recently announced support for [direction connections in Prisma Postgres](https://www.prisma.io/blog/orm-v6-17-0-new-usage-metrics-and-direct-connections-in-ga-for-prisma-postgres#using-prisma-postgres-with-any-tool-is-ready-for-production). This removes a big barrier for folks adopting Prisma Postgres as it allows for integrating with more tools in the wider Postgres ecosystem. Now, we’re taking this further by supporting connection pooling with this new approach. To add support for connection pooling, you can simply add the parameter `pool=true` to the connection string: + ``` postgres://555555..../postgres?sslmode=require&pool=true ``` + ## Local PPG without authentication in VS Code -If you’re not using the Prisma extension for VS Code, Cursor, or Windsurf, you’re missing out! Being able to manage your database right from your editor can reduce the context shift and help you get your app out faster. To make this even better, you can use a local Prisma Postgres database so you’re not tied to having an active internet connection. However, the main limitation with this is that you needed to logged in order to use it, which kind of defeats the point of having things local. +If you’re not using the Prisma extension for VS Code, Cursor, or Windsurf, you’re missing out! Being able to manage your database right from your editor can reduce the context shift and help you get your app out faster. To make this even better, you can use a local Prisma Postgres database so you’re not tied to having an active internet connection. However, the main limitation with this is that you needed to logged in order to use it, which kind of defeats the point of having things local. -Now, we’ve updated the extension to support local Prisma Postgres without having to login. This has been a [frequently requested feature](https://github.com/prisma/language-tools/pull/1914), so we’re happy to get this done. We’ve also cleaned up the UI a bit to have a clearer separation between what is your local databases, and what is your remote databases. +Now, we’ve updated the extension to support local Prisma Postgres without having to login. This has been a [frequently requested feature](https://github.com/prisma/language-tools/pull/1914), so we’re happy to get this done. We’ve also cleaned up the UI a bit to have a clearer separation between what is your local databases, and what is your remote databases. ![Local Prisma Postgres in VS Code without any login](/announcing-prisma-6-19-0/imgs/000a48b9be0569e18d23dd3c38c25fb2af113b9e-3840x1632.png) @@ -35,61 +37,61 @@ Now, we’ve updated the extension to support local Prisma Postgres without havi With this release, we’re very close to the next major release of Prisma, v7. This release will take all the new features that have been introduced over the course of v6 and make them the default. If you’ve been keeping up to date with all the releases, then you should be ready for v7! For others, please follow the minimal upgrade process 1. Updates to `schema.prisma` - - ```diff - generator client { - - provider = "prisma-client-js" - + provider = "prisma-client" - + output = "../src/generated/prisma" - + engineType = "client" - } - - - datasource db { - - provider = "postgresql" - - url = env("DATABASE_URL") - - } - ``` - + + ```diff + generator client { + - provider = "prisma-client-js" + + provider = "prisma-client" + + output = "../src/generated/prisma" + + engineType = "client" + } + + - datasource db { + - provider = "postgresql" + - url = env("DATABASE_URL") + - } + ``` + 2. Add any drivers you may need for your database - - If you’re using, for example PostgreSQL, you’ll need to [add the necessary driver](https://www.prisma.io/docs/orm/prisma-client/setup-and-configuration/no-rust-engine). In this case `@prisma/adapter-pg` can be used. If you’re using Prisma Postgres, then there’s no extra steps needed here. - - ```shell - npm install @prisma/adapter-pg - ``` - - Then in your `PrismaClient` setup, add the adapter as an optional parameter. - - ```tsx - import { PrismaClient } from './generated/prisma' - import { PrismaPg } from '@prisma/adapter-pg' - - const adapter = new PrismaPg({ connectionString: env.DATABASE_URL }) - const prisma = new PrismaClient({ adapter }) - ``` - -3. Migrate to `prisma.config.ts` - - The `prisma.config.ts` is the new home for configuring your Prisma project. - - ```tsx - import "dotenv/config"; - - import path from "node:path"; - import { defineConfig, env } from "prisma/config"; - - export default defineConfig({ - schema: path.join("prisma", "schema.prisma"), - migrations: { - path: path.join("prisma", "migrations"), - }, - engine: "classic", - datasource: { - url: env('DATABASE_URL'), - } - }); - ``` - + + If you’re using, for example PostgreSQL, you’ll need to [add the necessary driver](https://www.prisma.io/docs/orm/prisma-client/setup-and-configuration/no-rust-engine). In this case `@prisma/adapter-pg` can be used. If you’re using Prisma Postgres, then there’s no extra steps needed here. + + ```shell + npm install @prisma/adapter-pg + ``` + + Then in your `PrismaClient` setup, add the adapter as an optional parameter. + + ```tsx + import { PrismaClient } from "./generated/prisma"; + import { PrismaPg } from "@prisma/adapter-pg"; + + const adapter = new PrismaPg({ connectionString: env.DATABASE_URL }); + const prisma = new PrismaClient({ adapter }); + ``` + +3. Migrate to `prisma.config.ts` + + The `prisma.config.ts` is the new home for configuring your Prisma project. + + ```tsx + import "dotenv/config"; + + import path from "node:path"; + import { defineConfig, env } from "prisma/config"; + + export default defineConfig({ + schema: path.join("prisma", "schema.prisma"), + migrations: { + path: path.join("prisma", "migrations"), + }, + engine: "classic", + datasource: { + url: env("DATABASE_URL"), + }, + }); + ``` + For a full reference of the options that can be used in Prisma Config, see the [docs](https://www.prisma.io/docs/orm/reference/prisma-config-reference). Be sure to follow us on social media to stay up to date with all the latest release of Prisma ORM and Prisma Postgres diff --git a/apps/blog/content/blog/announcing-prisma-day-50cg22nn40qk/index.mdx b/apps/blog/content/blog/announcing-prisma-day-50cg22nn40qk/index.mdx index d63370801e..ea9159b3f6 100644 --- a/apps/blog/content/blog/announcing-prisma-day-50cg22nn40qk/index.mdx +++ b/apps/blog/content/blog/announcing-prisma-day-50cg22nn40qk/index.mdx @@ -11,7 +11,7 @@ heroImageAlt: "Announcing Prisma Day the future of data" --- Announcing the first [Prisma Day](https://www.prisma.io/day), a one-day Prisma community conference on the - intersection of modern app development, databases, and Prisma in production. +intersection of modern app development, databases, and Prisma in production. ## Prisma Day is coming to Berlin this June 📌 @@ -23,7 +23,7 @@ To celebrate, we are creating a one-day Prisma event to bring our vibrant online - 📍 **Location:** Berlin, ([Pfefferberg Theatre](https://pfefferberg-theater.de/)) - 👀 **Learn more:** [Prisma Day Page](https://www.prisma.io/day) -
+
{/* */} --- @@ -86,8 +86,7 @@ We do not tolerate any sort of discrimination or harassment based on gender, gen We are so excited to bring the community together for Prisma Day and showcase the amazing work happening throughout the data space. Developers all over the world are building incredible things with Prisma, and this day represents an opportunity for everyone to connect and share their experiences. -
+
{/* */} _Feel free to reach out with any questions to [day@prisma.io](mailto:day@prisma.io)_ - diff --git a/apps/blog/content/blog/announcing-prisma-orm-7-0-0/index.mdx b/apps/blog/content/blog/announcing-prisma-orm-7-0-0/index.mdx index 6c6aad0457..b89a35476e 100644 --- a/apps/blog/content/blog/announcing-prisma-orm-7-0-0/index.mdx +++ b/apps/blog/content/blog/announcing-prisma-orm-7-0-0/index.mdx @@ -24,28 +24,31 @@ With the ORM roadmap and Prisma Postgres launched, we had a solid foundation for ### Moving away from Rust -When we [launched 6.0.0](https://www.prisma.io/blog/prisma-6-better-performance-more-flexibility-and-type-safe-sql?utm_content=launch-blog&via=prisma7), we promised better performance, more flexibility, and better type-safety. We knew there was work to be done, and to get there, we needed to make some drastic changes. +When we [launched 6.0.0](https://www.prisma.io/blog/prisma-6-better-performance-more-flexibility-and-type-safe-sql?utm_content=launch-blog&via=prisma7), we promised better performance, more flexibility, and better type-safety. We knew there was work to be done, and to get there, we needed to make some drastic changes. -We announced we were [migrating the Prisma Client away from Rust](https://www.prisma.io/blog/series/prisma-orm-the-complete-rust-to-typescript-migration-journey?utm_source=blog&utm_content=launch-blog&via=prisma7) and rebuilding it in TypeScript. This might have seemed like a strange move to some, as Rust’s main premise is that it is fast and performant. But that is only half the story in our case. +We announced we were [migrating the Prisma Client away from Rust](https://www.prisma.io/blog/series/prisma-orm-the-complete-rust-to-typescript-migration-journey?utm_source=blog&utm_content=launch-blog&via=prisma7) and rebuilding it in TypeScript. This might have seemed like a strange move to some, as Rust’s main premise is that it is fast and performant. But that is only half the story in our case. A side effect of the client being built in Rust is that we were limiting who can contribute to the ORM. If you didn’t have strong Rust experience, it was far more difficult to make any meaningful contributions. On the technical side, the communication layer between Rust and the JavaScript runtime is much slower than doing things in plain JavaScript, plus it creates additional dependencies on the runtime. Our friends at Deno shared this sentiment: - -“We remember hearing about Prisma’s move away from Rust and thinking about how not dealing with the native addon API would make supporting Prisma in Deno so much simpler. We were all really excited to see it!" + + “We remember hearing about Prisma’s move away from Rust and thinking about how not dealing with + the native addon API would make supporting Prisma in Deno so much simpler. We were all really + excited to see it!" -Moving to a Rust-free client really set the stage for a faster client runtime, a smaller footprint, and simpler deployment story. You no longer needed to worry about runtime-specific quirks or infrastructure providers, like Cloudflare Workers, limiting the size of the deployed application. We continued on the path and kept iterating on what would become the Rust-free Prisma Client. The results? +Moving to a Rust-free client really set the stage for a faster client runtime, a smaller footprint, and simpler deployment story. You no longer needed to worry about runtime-specific quirks or infrastructure providers, like Cloudflare Workers, limiting the size of the deployed application. We continued on the path and kept iterating on what would become the Rust-free Prisma Client. The results? - 90% smaller bundle output - 3x faster query execution - Significantly lower CPU and memory utilization - Simpler deployments for Vercel Edge and Cloudflare Workers -And we have [the benchmarks to prove it](https://www.prisma.io/blog/prisma-orm-without-rust-latest-performance-benchmarks?utm_source=blog&utm_content=launch-blog&via=prisma7). The best part? You don’t need to rework your entire application to benefit from it. The changes required to switch are super simple. +And we have [the benchmarks to prove it](https://www.prisma.io/blog/prisma-orm-without-rust-latest-performance-benchmarks?utm_source=blog&utm_content=launch-blog&via=prisma7). The best part? You don’t need to rework your entire application to benefit from it. The changes required to switch are super simple. ```prisma // schema.prisma @@ -54,15 +57,17 @@ generator client { + provider = "prisma-client" // rust-free and ESM } ``` + We’ve been talking to dozens of users from the community, as well as close friends like Kent C. Dodds, and the feedback has been very positive: - -“I upgraded a few weeks ago and it was great to see how well everything went and how easy it was to switch to the new Rust-Free Client." + + “I upgraded a few weeks ago and it was great to see how well everything went and how easy it was + to switch to the new Rust-Free Client." - ### Generated code out of `node_modules` and a new config file Additionally, we've taken the feedback regarding how we handle generated artifacts and made it better and more universally compatible. We [covered this some time ago](https://www.prisma.io/blog/why-prisma-orm-generates-code-into-node-modules-and-why-it-ll-change?utm_source=blog&utm_content=launch-blog&via=prisma7), but we historically generated the client in your project's `node_modules`. At the time, this made sense as we wanted to make working with the generated client feel like any other library. But as time has progressed, we've found that this impacts developer workflows significantly. If the client needs to be updated, any app-specific processes need to be stopped first before regenerating the types. Instead, we default to generating the types and Prisma Client in your project source code, so your existing dev and build tools see it as part of your app. Now if you make changes to your models and run `prisma generate`, tools and file watchers can react to these changes and keep your dev workflow running. Your whole setup just becomes a part of your project, not something that is obscured in `node_modules`. @@ -71,32 +76,36 @@ We also added support for dynamic project configuration with the new Prisma conf ### Faster and fewer types -But this wasn’t all we focused on. One of Prisma ORM’s main benefits is type-safety. This means making sure we not only provide the correct types, but do so in a way that is fast and efficient. To improve this, we collaborated with David Blass, the creator of ArkType, to [evaluate how well](https://www.prisma.io/blog/why-prisma-orm-checks-types-faster-than-drizzle?utm_source=blog&utm_content=launch-blog&via=prisma7) we were generating our types. The results are well worth a read, but the highlights include: +But this wasn’t all we focused on. One of Prisma ORM’s main benefits is type-safety. This means making sure we not only provide the correct types, but do so in a way that is fast and efficient. To improve this, we collaborated with David Blass, the creator of ArkType, to [evaluate how well](https://www.prisma.io/blog/why-prisma-orm-checks-types-faster-than-drizzle?utm_source=blog&utm_content=launch-blog&via=prisma7) we were generating our types. The results are well worth a read, but the highlights include: - Prisma requires ~98% fewer types to evaluate a schema - Prisma requires ~45% fewer types for query evaluation - Prisma is 70% faster when performing a full type check -Compared to other ORMs in the ecosystem, our generated types were not only faster to evaluate, but required fewer types in order for TypeScript to provide useful information to the user. Why does this matter? Because developers can build their applications with confidence knowing that the type-safety we offer is not only fast, but has less overhead. +Compared to other ORMs in the ecosystem, our generated types were not only faster to evaluate, but required fewer types in order for TypeScript to provide useful information to the user. Why does this matter? Because developers can build their applications with confidence knowing that the type-safety we offer is not only fast, but has less overhead. ### Prisma Postgres for all But Prisma is more than just an ORM. Our managed Postgres database was built to give developers the thing they need when they get started with Prisma ORM… a database. We built Prisma Postgres with bare-metal infrastructure powered by unikernel microVMs. All that means is that things are fast, and stay fast. But we kept the developer experience in mind, handling the complex parts so you don’t have to manage them yourself. Instead of having to worry about provisioning or resource configuration, we manage all that for you, and integrate natively with Prisma ORM. The same great DX you're used to from the ORM, also for your database. -Getting started with Prisma Postgres is as simple as running a single command from your terminal: +Getting started with Prisma Postgres is as simple as running a single command from your terminal: ```shell npm create db ``` + You’ll get a database provisioned for you, and a link to claim it once you’re ready. For those using AI agents in their developer workflows, we provide a dedicated API and MCP server that can be used to create and manage databases as they are needed. - -“Whenever I work with Prisma, I get to the getting started guide, install things and then realize I needed to go get a database. I’d always get lost in that process so being able to create a database this easily is amazing!” + + “Whenever I work with Prisma, I get to the getting started guide, install things and then realize + I needed to go get a database. I’d always get lost in that process so being able to create a + database this easily is amazing!” -Prisma Postgres hasn’t stopped there though. We have now adopted the standard Postgres connection protocol so that any tool in the wider ecosystem can talk to your database. This means tools like Cloudflare Hyperdrive, TablePlus, Retool, or even other ORMs can all be used with Prisma Postgres. +Prisma Postgres hasn’t stopped there though. We have now adopted the standard Postgres connection protocol so that any tool in the wider ecosystem can talk to your database. This means tools like Cloudflare Hyperdrive, TablePlus, Retool, or even other ORMs can all be used with Prisma Postgres. All of this is possible because Prisma Postgres is just standard Postgres, but built to provide the best Postgres experience. diff --git a/apps/blog/content/blog/announcing-prisma-orm-7-2-0/index.mdx b/apps/blog/content/blog/announcing-prisma-orm-7-2-0/index.mdx index 1d62d04ce1..556d320784 100644 --- a/apps/blog/content/blog/announcing-prisma-orm-7-2-0/index.mdx +++ b/apps/blog/content/blog/announcing-prisma-orm-7-2-0/index.mdx @@ -28,6 +28,7 @@ npm install @prisma/client@latest # The adapter for your database npm install @prisma/adapter-pg@latest ``` + ## Bringing back the `--url` flag to Prisma CLI In v7, we removed the `--url` flag from various `prisma db` subcommands. This flag was mostly undocumented, and we didn’t think many people were using it. However, community feedback showed that this flag was extremely useful. As a result, we’ve brought it back in v7.2.0 and made updates to support the new Prisma config setup. @@ -60,6 +61,7 @@ export default defineConfig({ }, }); ``` + This works most of the time, but it makes some assumptions that aren’t always valid. For instance, we explicitly import `dotenv` here, which is only needed in Node and not required in Bun. To address this, we’ve added runtime-specific config output based on the JavaScript environment. If `prisma init` is run in Bun, it will generate the following: @@ -78,6 +80,7 @@ export default defineConfig({ }, }); ``` + For Node environments (`npx`, `pnpm`, `bunx`, `bun run`): ```tsx @@ -96,6 +99,7 @@ export default defineConfig({ }, }); ``` + Here, we still need `dotenv` and can remove the need to use the `env` helper function. ## Optional URL for `prisma generate` and better error messages @@ -107,11 +111,13 @@ We also improved error messages when you choose to use the `env` helper function ```shell Failed to load config file "[path]/prisma.config.ts" as a TypeScript/JavaScript module. Error: PrismaConfigEnvError: Cannot resolve environment variable: UNDEFINED_VARIABLE ``` + As a bonus, if you try to run `prisma db ...` without a valid URL, you’ll now get a clearer error message: ```shell Error: The datasource.url property is required in your Prisma config file when using prisma db push. ``` + ## `[DecimalError] Invalid argument` when using the `@db.Money` type If you’ve been using the `@db.Money` type in your Prisma schema, you might have run into an issue where values over 999.99 would throw an error like `[DecimalError] Invalid argument: X,XXX`. Parsing money values from Postgres is complicated due to locale differences and the wide range of accepted formats. @@ -129,4 +135,3 @@ Be sure to follow us on social media to stay up to date with the latest releases - [YouTube](https://www.youtube.com/@PrismaData) - [LinkedIn](https://www.linkedin.com/company/prisma-io/) - [Discord](https://pris.ly/discord) - diff --git a/apps/blog/content/blog/announcing-prisma-playground-xeywknkj0e1p/index.mdx b/apps/blog/content/blog/announcing-prisma-playground-xeywknkj0e1p/index.mdx index 23df80aa7d..51bfa967be 100644 --- a/apps/blog/content/blog/announcing-prisma-playground-xeywknkj0e1p/index.mdx +++ b/apps/blog/content/blog/announcing-prisma-playground-xeywknkj0e1p/index.mdx @@ -36,7 +36,6 @@ If you're not yet using Prisma, be sure to visit the Playground to explore the P [Try Prisma](https://playground.prisma.io) - ## Prisma Playground: An interactive and isolated sandbox environment When getting started with the Prisma Playgroud, you can choose whether you want to learn about Prisma Client or Prisma Migrate. @@ -68,4 +67,3 @@ For Prisma Migrate, the Playground offers interactive guides that give instructi We encourage you to [try out the Playground](https://playground.prisma.io/) and let us know what you think! While we have many ideas for future applications and other areas where the Playground can be useful, we're very much interested in hearing from _you_ what you like to see! Share your feedback with us in the feedback box at the end of each guide. - diff --git a/apps/blog/content/blog/announcing-prisma-postgres-early-access/index.mdx b/apps/blog/content/blog/announcing-prisma-postgres-early-access/index.mdx index 8ad80c1ae1..bef0b76bd4 100644 --- a/apps/blog/content/blog/announcing-prisma-postgres-early-access/index.mdx +++ b/apps/blog/content/blog/announcing-prisma-postgres-early-access/index.mdx @@ -31,7 +31,7 @@ Prisma Postgres is now available in Early Access 🎉 ## A new era for "serverless" PostgreSQL -Our new architecture takes a fundamentally different approach to provisioning databases compared to existing cloud providers and other serverless database products. In this section, we'll take a look at the trend of serverless databases and explain how Prisma Postgres fits into this category. Jump ahead to the [next section](##building-a-managed-postgresql-service-on-millisecond-cloud-infrastructure) if you care more about our architecture and technical details. +Our new architecture takes a fundamentally different approach to provisioning databases compared to existing cloud providers and other serverless database products. In this section, we'll take a look at the trend of serverless databases and explain how Prisma Postgres fits into this category. Jump ahead to the [next section](##building-a-managed-postgresql-service-on-millisecond-cloud-infrastructure) if you care more about our architecture and technical details. ### What is a serverless database? @@ -45,7 +45,7 @@ The promise of auto-scaling often means that a database can scale down to zero, ![](/announcing-prisma-postgres-early-access/imgs/36a57a880128c93e52193c963fc0c9889ea9df5a-1282x504.png) -*Source: [Vercel Docs | Vercel Postgres Limits](https://vercel.com/docs/storage/vercel-postgres/limits#vercel-postgres-cold-starts)* +_Source: [Vercel Docs | Vercel Postgres Limits](https://vercel.com/docs/storage/vercel-postgres/limits#vercel-postgres-cold-starts)_ Some database providers even require manual action from developers to wake a suspended database. While serverless databases have their advantages, cold starts are a major drawback! @@ -67,13 +67,15 @@ What if you could get all the benefits of "serverless" without the drawbacks, li Today, we are thrilled to share the first step in this direction: We've teamed up with [Unikraft](https://unikraft.cloud/) to provision PostgreSQL instances using [Unikraft Cloud](https://unikraft.cloud/docs/), a groundbreaking millisecond cloud platform that eliminates cold starts, enables millisecond scale-to-zero and auto-scale, and allows for thousands of instances to run on a single bare metal machine. - -
- Cold starts are a real pain. Unikraft Cloud provides cold starts in the order of a few milliseconds with hardware-level isolation. +
+ Cold starts are a real pain. Unikraft Cloud provides cold starts in the order of a few + milliseconds with hardware-level isolation.
@@ -92,7 +94,7 @@ Prisma Postgres is neither. Instead, it uses Unikraft Cloud and is based on a ne ### An overview of the Unikraft Cloud stack -Let's take a closer look at Unikraft Cloud's millisecond cloud infrastructure: +Let's take a closer look at Unikraft Cloud's millisecond cloud infrastructure: ![](/announcing-prisma-postgres-early-access/imgs/8cf5361a40fdc079b0db4b848483b59f2e99d50b-1740x232.png) @@ -104,7 +106,7 @@ To provide high efficiency and millisecond semantics, the Unikraft team had to o ### No cloud provider: Prisma Postgres runs on bare metal -We are building Prisma Postgres from first principles, striking the perfect balance between performance, cost, safety, and ease of use. +We are building Prisma Postgres from first principles, striking the perfect balance between performance, cost, safety, and ease of use. To have _full_ control and avoid the limitations, constraints, and pricing models of major cloud providers, we chose to lease our own physical machines in data centers around the globe. @@ -116,12 +118,11 @@ Prisma Postgres is based on the observation that _modern hardware is incredibly companyLink="https://basecamp.com/cloud-exit" companyName="Basecamp Blog" > -
- Leaving the cloud will save us $7 million over five years. +
+ Leaving the cloud will save us $7 million over five years.
- In short: With Prisma Postgres, your database will run on powerful servers backed by high CPU core counts, large amounts of RAM, and super-fast NVME storage. ### Unikernels running as microVMs with Unikraft and Firecracker @@ -129,7 +130,7 @@ In short: With Prisma Postgres, your database will run on powerful servers backe One of the core components of our architecture is the deployment of PostgreSQL inside _unikernels running as lightweight microVMs_. > Unikernels are famous for providing excellent performance in terms of boot times, throughput and memory consumption, to name a few metrics. -> +> > [Unikraft: Fast, Specialized Unikernels the Easy Way](https://dl.acm.org/doi/abs/10.1145/3447786.3456248) (Research paper, EuroSys 21) Over the past few months, we've collaborated closely with the Unikraft team and have been deeply impressed by their work of increasing DX and making unikernels more approachable for developers. Our conclusion is clear: **Unikernels are finally ready to be adopted for high-performance production workloads.** @@ -144,7 +145,7 @@ Through an extra compilation step, Unikraft turns a traditional fullstack applic ![](/announcing-prisma-postgres-early-access/imgs/d7a8980c3be1841af6c46fee0683b93179052358-1114x592.png) -*Source: [Unikraft Cloud - How it works](https://unikraft.cloud/how-it-works)* +_Source: [Unikraft Cloud - How it works](https://unikraft.cloud/how-it-works)_ ### The Prisma Postgres unikernel binary is 5 times smaller than the original PostgreSQL image @@ -152,13 +153,11 @@ We've created the Prisma Postgres unikernel binary image in close collaboration ![](/announcing-prisma-postgres-early-access/imgs/c5430a6cd6e3437dc71d217e3bee9d3b4755788c-2510x1290.png) - The Unikraft team managed to trim the original PostgreSQL image down from 280MB to 61MB. Here's a breakdown of the components of the **Prisma Postgres image**: - ![](/announcing-prisma-postgres-early-access/imgs/c356d781c1816fad242fac28520fbe6199a9deae-2888x1858.png) -We reduced the image to about 20% of its original size by identifying and removing unnecessary packages from our deployment. The original PostgreSQL image includes a lot of generic functionality that isn't needed for Prisma Postgres. +We reduced the image to about 20% of its original size by identifying and removing unnecessary packages from our deployment. The original PostgreSQL image includes a lot of generic functionality that isn't needed for Prisma Postgres. In our architecture, these specialized binary images are deployed as unikernels on our bare metal machines; and, as unikernels are ultimately virtual machines, each PostgreSQL instance provides strong, hardware-level isolation. @@ -179,8 +178,7 @@ Everything in between the two is overhead. A unikernel adds the thinnest possibl ![](/announcing-prisma-postgres-early-access/imgs/178696d9a18e3803db87032122e7ade619554c4e-2888x1236.png) -*Source: [Unikraft Docs - Virtualization](https://unikraft.org/docs/concepts/virtualization)* - +_Source: [Unikraft Docs - Virtualization](https://unikraft.org/docs/concepts/virtualization)_ Here's a summary of the advantages unikernels have in comparison to standard VMs and containers: @@ -199,7 +197,6 @@ Our architecture enables us to avoid cold starts while providing all the benefit - **MicroVMs boot in milliseconds**: Unlike traditional virtual machines that take longer to initialise due to their larger overhead (BIOS, full OS, etc.), our microVMs are stripped of unnecessary components and can be launched in milliseconds. - **Multi-tier VM snapshotting:** Unikraft VMs combine the virtual machine monitor (VMM) of [Firecracker](https://firecracker-microvm.github.io/) with the packaging characteristics of unikernels. Firecracker makes it possible to create and restore VM memory snapshots, enabling a restore of a machine from hibernation in single-digit milliseconds. For Prisma Postgres, we are building a multi-tier snapshotting system that'll efficiently manage snapshots. This capability will allow us to hibernate databases after short durations of inactivity while still being capable of serving a request with negligible startup overhead. - ## A fully integrated data layer with a DB, ORM, edge caching, connection pooling & more Prisma Postgres is a major step towards our vision of providing a fully integrated data layer for global applications. @@ -225,7 +222,8 @@ The fastest way to try Prisma Postgres is by following the instructions here:

-After setting up the database, you will receive instructions for downloading and running a sample project. +After setting up the database, you will receive instructions for downloading and running a sample +project. ### What you get with Prisma Postgres today @@ -235,7 +233,7 @@ Prisma Postgres is launching in Early Access today. During the Early Access phas - Prisma Postgres always comes bundled with Accelerate, this means you get connection pooling and edge caching out-of-the box. - Prisma Postgres charges per query, for egress, and for storage/delivery of database events. You can find all details on our [pricing](https://www.prisma.io/pricing) page. -Also check out the [documentation](https://www.prisma.io/docs/orm/overview/databases/prisma-postgres) to learn about current limitations of Prisma Postgres. +Also check out the [documentation](https://www.prisma.io/docs/orm/overview/databases/prisma-postgres) to learn about current limitations of Prisma Postgres. ### Try Prisma Postgres diff --git a/apps/blog/content/blog/announcing-prisma-postgres-for-ai-coding-agents/index.mdx b/apps/blog/content/blog/announcing-prisma-postgres-for-ai-coding-agents/index.mdx index de83130cc7..a3b0c44086 100644 --- a/apps/blog/content/blog/announcing-prisma-postgres-for-ai-coding-agents/index.mdx +++ b/apps/blog/content/blog/announcing-prisma-postgres-for-ai-coding-agents/index.mdx @@ -17,7 +17,7 @@ The rapidly evolving AI coding agent landscape demands new infrastructure soluti The concept of "vibe coding" – describing what you want to an AI and letting it generate the code – has captured developers' imagination. The results can be impressive: functioning prototypes materialized from simple descriptions, complex algorithms implemented without wrestling with syntax, and UIs assembled without painstaking CSS tweaks. - + But any developer who has experimented with this approach inevitably runs into its limitations. After a few rounds of prompts, what started as a clean implementation often becomes unwieldy. Subtle bugs creep in that become increasingly difficult to resolve. The promise of effortless development gives way to a familiar frustration — now compounded by code you didn't write yourself. @@ -82,9 +82,11 @@ This is where solutions like [Prisma Postgres](https://www.prisma.io/postgres) p It offers the reliability developers need without the operational complexity that AI can't abstract away. When AI helps you move faster on code generation, you need infrastructure that keeps pace – scalable, on-demand, and requiring minimal configuration. To try out Prisma Postgres, simply run this command in your terminal: + ``` npx prisma init --db ``` + Prisma Postgres also integrates directly with your favorite LLM and AI coding environment via the Model Context Protocol (MCP). Simply add it to the MCP configuration of your desired AI tool using this JSON snippet: @@ -99,6 +101,7 @@ Simply add it to the MCP configuration of your desired AI tool using this JSON s } } ``` + Learn more about this in the [Prisma Postgres documentation](https://www.prisma.io/docs/postgres/mcp-server). ## When to use AI? diff --git a/apps/blog/content/blog/announcing-prisma-s-mcp-server-vibe-code-with-prisma-postgres/index.mdx b/apps/blog/content/blog/announcing-prisma-s-mcp-server-vibe-code-with-prisma-postgres/index.mdx index f95456fcf9..f8c2f01721 100644 --- a/apps/blog/content/blog/announcing-prisma-s-mcp-server-vibe-code-with-prisma-postgres/index.mdx +++ b/apps/blog/content/blog/announcing-prisma-s-mcp-server-vibe-code-with-prisma-postgres/index.mdx @@ -28,6 +28,7 @@ We're excited to share that as of [v6.6.0](https://github.com/prisma/prisma/rele } } ``` + If you're curious what exactly the integration looks like in any specific tool, [check out our docs](https://www.prisma.io/docs/postgres/mcp-server) with instructions for adding the MCP server to **Cursor**, **Windsurf**, **Claude Code / Desktop** and the **OpenAI Agents SDK**. ## Prisma Postgres: The database designed for the age of AI @@ -38,21 +39,18 @@ It offers the reliability developers need without the operational complexity tha ## "Vibe code" your apps with Prisma Postgres -_Vibe coding_ has been a major trend lately — it describes developers building their applications purely based on prompting and AI-generated code. +_Vibe coding_ has been a major trend lately — it describes developers building their applications purely based on prompting and AI-generated code. -While vibe coded apps don't yet cross the threshold of complexity required for serious application development, the wider trend of AI-assisted coding is certainly reshaping the software development industry! Repetitive and monotonous coding tasks, features with very detailed specifications and prototyping are areas that are seeing major productivity boosts by AI and let developers build applications at new speed levels. +While vibe coded apps don't yet cross the threshold of complexity required for serious application development, the wider trend of AI-assisted coding is certainly reshaping the software development industry! Repetitive and monotonous coding tasks, features with very detailed specifications and prototyping are areas that are seeing major productivity boosts by AI and let developers build applications at new speed levels. **In this new world, the pace of _writing code_ often isn't the bottleneck any more — it's rather the tasks that AI can't help with: Provisioning, configuring and managing infrastructure.** -With Prisma's new MCP server, you can reduce the infrastructure management overhead and align it with the speed at which you're writing code thanks to your powerful AI assistants. +With Prisma's new MCP server, you can reduce the infrastructure management overhead and align it with the speed at which you're writing code thanks to your powerful AI assistants. ### First-class integration with Cursor, Windsurf, Claude Code and any other AI tool The most popular AI coding tools all offer first-class integrations for MCP servers. Here's how you can add it to your favorite tool: - - - ```js // File: `~/.cursor/mcp.json` { @@ -65,6 +63,7 @@ The most popular AI coding tools all offer first-class integrations for MCP serv } } ``` + ```js // File: `~/.codeium/windsurf/mcp_config.json` { @@ -77,16 +76,15 @@ The most popular AI coding tools all offer first-class integrations for MCP serv } } ``` + ```shell # In you terminal claude mcp add prisma npx prisma mcp ``` - - ### What to do with Prisma's MCP server? -Prisma's MCP server allows you to chat in natural language through database provisioning, data modeling and migration workflows. +Prisma's MCP server allows you to chat in natural language through database provisioning, data modeling and migration workflows. It connects your AI assistant to the [Prisma Console](https://console.prisma.io) and lets it perform these tasks on your behalf. Here are some things the MCP server enables: @@ -97,9 +95,11 @@ It connects your AI assistant to the [Prisma Console](https://console.prisma.io) ### Scaffold new projects with: `prisma init --vibe` 😎 As a bonus, we add a `--vibe` (alias for `--prompt`) option to the `prisma init` command which is going to scaffold a Prisma schema and deploy it to a fresh Prisma Postgres instance for you: + ``` npx prisma init --vibe "Cat meme generator" ``` + ## Tell us what you're building with Prisma & AI AI is enabling new levels of developer productivity and we're excited to contribute to this new era with the world's most efficient Postgres database! We'd love to hear from you: What Prisma-backed applications are you building with the powers of AI? Tell us on [X](https://pris.ly/x?utm_source=blog&utm_medium=conclusion) or on our [Discord](https://pris.ly/discord??utm_source=blog&utm_medium=conclusion)! diff --git a/apps/blog/content/blog/announcing-query-insights-for-prisma-postgres/index.mdx b/apps/blog/content/blog/announcing-query-insights-for-prisma-postgres/index.mdx index 6bd0e3bab6..6e73853a23 100644 --- a/apps/blog/content/blog/announcing-query-insights-for-prisma-postgres/index.mdx +++ b/apps/blog/content/blog/announcing-query-insights-for-prisma-postgres/index.mdx @@ -23,7 +23,7 @@ The hard part is getting the answer. A request takes longer than usual. An endpoint starts timing out. Database usage increases. But it is often difficult to understand which queries are responsible, how often they run, and how much load they create. -This matters even more now that many developers are building with AI tools. +This matters even more now that many developers are building with AI tools. AI can help you move faster, but it can also generate database logic that does more work than necessary. A single user action can end up triggering many queries instead of a small number of efficient ones, which increases latency and database usage. @@ -95,14 +95,8 @@ Query Insights is included with **Prisma Postgres** and there is nothing to enab Create a database, open the **Queries** tab, and start exploring how your queries behave in production. - This is just the beginning! We are continuing to improve Query Insights and add more capabilities. diff --git a/apps/blog/content/blog/announcing-the-release-of-nexus-schema-v1-b5eno5g08d0b/index.mdx b/apps/blog/content/blog/announcing-the-release-of-nexus-schema-v1-b5eno5g08d0b/index.mdx index 04bf515ac3..80fff884b1 100644 --- a/apps/blog/content/blog/announcing-the-release-of-nexus-schema-v1-b5eno5g08d0b/index.mdx +++ b/apps/blog/content/blog/announcing-the-release-of-nexus-schema-v1-b5eno5g08d0b/index.mdx @@ -39,17 +39,19 @@ type Query { posts: [Post]! } ``` + ```ts const Query = { posts: () => [ { - id: '1', - title: 'My first GraphQL server', - body: 'How I wrote my first GraphQL server', + id: "1", + title: "My first GraphQL server", + body: "How I wrote my first GraphQL server", }, ], -} +}; ``` + While the schema-first approach is easy to get started with, it comes with some inherent drawbacks that can make development difficult when applications start getting bigger. Nexus takes a different approach to building GraphQL APIs. Instead of keeping a separate schema and set of resolvers, with Nexus, schemas and resolvers are written in the same spot using code. @@ -57,34 +59,35 @@ Nexus takes a different approach to building GraphQL APIs. Instead of keeping a Refactoring the Post example above to Nexus would look like this: ```ts -import { objectType, queryType, makeSchema } from 'nexus' +import { objectType, queryType, makeSchema } from "nexus"; const Post = objectType({ - name: 'Post', + name: "Post", definition(t) { - t.id('id') - t.string('title') - t.string('body') + t.id("id"); + t.string("title"); + t.string("body"); }, -}) +}); const Query = queryType({ definition(t) { - t.list.field('posts', { + t.list.field("posts", { resolve: () => [ { - id: '1', - title: 'My first GraphQL server', - body: 'How I wrote my first GraphQL server', + id: "1", + title: "My first GraphQL server", + body: "How I wrote my first GraphQL server", }, ], - }) + }); }, -}) +}); const schema = makeSchema({ types: [Post, Query], -}) +}); ``` + There are numerous benefits to taking a code-first approach with Nexus, including: - Schema and resolver co-location @@ -106,15 +109,16 @@ One major benefit of using Nexus is its ability to automatically generate TypeSc Type and SDL generation comes for free with Nexus and can be enabled by supplying some configuration in the `makeSchema` call. ```ts -import path from 'path' +import path from "path"; const schema = makeSchema({ types: [Post, Query], outputs: { - schema: path.join(__dirname, 'generated/schema.gen.graphql'), - typegen: path.join(__dirname, 'generated/nexusTypes.gen.ts'), + schema: path.join(__dirname, "generated/schema.gen.graphql"), + typegen: path.join(__dirname, "generated/nexusTypes.gen.ts"), }, -}) +}); ``` + ## What's New at Nexus 1.0? There are a number of changes to Nexus at 1.0. Read the [full changelog](https://github.com/graphql-nexus/nexus/releases/tag/1.0.0) and follow along below to see what's new! @@ -124,9 +128,10 @@ There are a number of changes to Nexus at 1.0. Read the [full changelog](https:/ Nexus 1.0 is now available under the `nexus` package name. All imports now come from `nexus` and not `@nexus/schema`. ```ts -import { makeSchema } from 'nexus' +import { makeSchema } from "nexus"; // ... ``` + ### Changes to Nullability In previous versions of Nexus, fields were treated as non-nullable by default. This differed from other GraphQL API frameworks which would treat fields as nullable unless otherwise specified. The Nexus authors took this approach because allowing fields to be non-nullable by default posed some long-term risks for API development. @@ -137,14 +142,15 @@ At Nexus 1.0, you need to explicitly make fields non-null: ```ts const Post = objectType({ - name: 'Post', + name: "Post", definition(t) { - t.nonNull.id('id') - t.nonNull.string('title') - t.nonNull.string('body') + t.nonNull.id("id"); + t.nonNull.string("title"); + t.nonNull.string("body"); }, -}) +}); ``` + The SDL for this type would look like this: ```graphql @@ -154,6 +160,7 @@ type Post { body: String! } ``` + The code for the `Post` object type above uses a property called `nonNull` which offers a bit more flexibility for specifying that fields should be non-null. It's useful for situations where the chaining API is not ideal, such as expressing deeply-nested types and when programmatically creating non-nullable types. ```ts @@ -173,37 +180,35 @@ queryType({ } }) ``` + The `nonNull` function accepts an argument which can be used to specify the type for a type or argument. While fields are now nullable by default, it's possible to change this behavior either globally or at the type level in your Nexus API. - - ```ts queryType({ nonNullDefaults: { output: true, }, definition(t) { - t.string('echo', { + t.string("echo", { args: { - message: 'String', + message: "String", }, resolve(_root, args) { - return args.message + return args.message; }, - }) + }); }, -}) +}); ``` - In this example, Nexus now expects that fields should be non-null for all query type fields. If you choose to change a type to be non-null by default, you can use the `nullable` function to specify that certain fields should be nullable. ```ts -import { queryType, stringArg, nullable } from 'nexus' +import { queryType, stringArg, nullable } from "nexus"; queryType({ nonNullDefaults: { @@ -211,18 +216,19 @@ queryType({ output: true, }, definition(t) { - t.field('echo', { - type: nullable('String'), + t.field("echo", { + type: nullable("String"), args: { message: nullable(stringArg()), }, resolve(_root, args) { - return args.message + return args.message; }, - }) + }); }, -}) +}); ``` + To find out more about how Nexus handles nullability, including other ways to interact with the API, read the [Nullability guide](https://nexusjs.org/docs/guides/nullability). ### Changes to the List API @@ -246,6 +252,7 @@ queryType({ } }) ``` + The same chaining API for creating lists still remain, but the `list` function exist to help for situations where chaining is not ideal. ### Abstract Types @@ -266,19 +273,20 @@ The [Centralized strategy](https://nexusjs.org/docs/guides/abstract-types#centra ```ts const SearchResult = unionType({ - name: 'SearchResult', + name: "SearchResult", resolveType(data) { - const __typename = data.album ? 'Song' : data.rating ? 'Movie' : data.width ? 'Photo' : null + const __typename = data.album ? "Song" : data.rating ? "Movie" : data.width ? "Photo" : null; if (!__typename) { - throw new Error(`Could not resolve the type of data passed to union type "SearchResult"`) + throw new Error(`Could not resolve the type of data passed to union type "SearchResult"`); } - return __typename + return __typename; }, definition(t) { - t.members('Photo', 'Movie', 'Song') + t.members("Photo", "Movie", "Song"); }, -}) +}); ``` + #### Discriminant Model Field Strategy (`__typename`) The [Discriminant Model Field (DMF) strategy](https://nexusjs.org/docs/guides/abstract-types#discriminant-model-field-dmf-strategy-__typename) allows you to discriminate your union member types in a potentialy modular way. It is based on supplying at \_\_typename field in the model data returned by resolvers of fields typed as an abstract type. Here is an example: @@ -286,66 +294,76 @@ The [Discriminant Model Field (DMF) strategy](https://nexusjs.org/docs/guides/ab ```ts const Query = queryType({ definition(t) { - t.field('search', { - type: 'SearchResult', + t.field("search", { + type: "SearchResult", args: { pattern: stringArg(), }, resolve(root, args, ctx) { - return ctx.db.search(args.pattern).map(result => { - const __typename = result.album ? 'Song' : result.rating ? 'Movie' : result.width ? 'Photo' : null + return ctx.db.search(args.pattern).map((result) => { + const __typename = result.album + ? "Song" + : result.rating + ? "Movie" + : result.width + ? "Photo" + : null; if (!__typename) { - throw new Error(`Could not resolve the type of data passed to union type "SearchResult"`) + throw new Error( + `Could not resolve the type of data passed to union type "SearchResult"`, + ); } return { ...result, __typename, - } - }) + }; + }); }, - }) + }); }, -}) +}); ``` + #### Modular Strategy (`isTypeOf`) The [Modular strategy](https://nexusjs.org/docs/guides/abstract-types#modular-strategy-istypeof) allows you to discriminate your union member types in a modular way (surprise). It uses a predicate function that you implement that allows Nexus (actually GraphQL.js under the hood) to know at runtime if data being sent to the client is of a respective type or not. Here is an example: ```ts const Movie = objectType({ - name: 'Movie', + name: "Movie", isTypeOf(data) { - return Boolean(data.rating) + return Boolean(data.rating); }, definition(t) { - t.string('url') - t.field('rating', { - type: 'MovieRating', - }) + t.string("url"); + t.field("rating", { + type: "MovieRating", + }); }, -}) +}); const Photo = objectType({ - name: 'Photo', + name: "Photo", isTypeOf(data) { - return Boolean(data.width) + return Boolean(data.width); }, definition(t) { - t.string('url') - t.int('width') - t.int('height') + t.string("url"); + t.int("width"); + t.int("height"); }, -}) +}); const Song = objectType({ - name: 'Song', + name: "Song", isTypeOf(data) { - return Boolean(data.album) + return Boolean(data.album); }, definition(t) { - t.string('url') - t.string('album') + t.string("url"); + t.string("album"); }, -}) +}); ``` + Read the [Abstract Types guide](https://nexusjs.org/docs/guides/abstract-types) for details on how strategies work, are enabled or disabled, are type safe, and more. ### Changes to Backing Types @@ -384,4 +402,3 @@ The Nexus playground has been converted to a set of prepared Codesandbox example Nexus 1.0 represents a significant milestone in the library's evolution. The community has played a crucial role in the its formation and success over the past 2+ years. We'd like to thank everyone who has tried out Nexus, submitted issues and pull requests, and has put it into production! Follow [@nexusgql on Twitter](https://twitter.com/nexusgql) and watch [the repo](https://github.com/graphql-nexus/nexus) to get future updates on what's happening with Nexus. - diff --git a/apps/blog/content/blog/announcing-typedsql-make-your-raw-sql-queries-type-safe-with-prisma-orm/index.mdx b/apps/blog/content/blog/announcing-typedsql-make-your-raw-sql-queries-type-safe-with-prisma-orm/index.mdx index 98644c01da..5dee489461 100644 --- a/apps/blog/content/blog/announcing-typedsql-make-your-raw-sql-queries-type-safe-with-prisma-orm/index.mdx +++ b/apps/blog/content/blog/announcing-typedsql-make-your-raw-sql-queries-type-safe-with-prisma-orm/index.mdx @@ -14,23 +14,21 @@ tags: - "announcement" --- -With today’s [v5.19.0](https://github.com/prisma/prisma/releases/tag/5.19.0) release, Prisma ORM introduces a new way to write raw SQL queries in a type-safe way! You now get the best of both worlds with Prisma ORM: A convenient high-level abstraction for the majority of queries _and_ a flexible, type-safe escape hatch for raw SQL. +With today’s [v5.19.0](https://github.com/prisma/prisma/releases/tag/5.19.0) release, Prisma ORM introduces a new way to write raw SQL queries in a type-safe way! You now get the best of both worlds with Prisma ORM: A convenient high-level abstraction for the majority of queries _and_ a flexible, type-safe escape hatch for raw SQL. ## TL;DR: We made raw SQL fully type-safe -With Prisma ORM, we have designed what we believe to be the best API to write regular CRUD queries that make up 95% of most apps! +With Prisma ORM, we have designed what we believe to be the best API to write regular CRUD queries that make up 95% of most apps! For the remaining 5% — the complex queries that either can't be expressed with the Prisma Client API or rCopy the port number from the terminal output, you’ll need it in the next step for the pg_restore command.equire maximum performance — we have provided a lower level API to write raw SQL. However, this escape hatch didn't offer type safety and developers were missing the great DX they were used to from Prisma ORM, so we looked for a better way! With today’s Prisma ORM [v5.19.0](https://github.com/prisma/prisma/releases/tag/5.19.0) release, we are thrilled to announce TypedSQL: The best way to write complex and highly performant queries. **[TypedSQL](https://www.prisma.io/typedsql) is just SQL, but better.** It’s fully type-safe, provides auto-completion, and gives you a fantastic DX whenever you need to craft raw SQL queries. Here’s how it works: -1. Write a SQL query in a `.sql` file and put it into the `prisma/sql` directory: - - +1. Write a SQL query in a `.sql` file and put it into the `prisma/sql` directory: ```sql -- prisma/sql/conversionByVariant.sql - + SELECT "variant", CAST("checked_out" AS FLOAT) / CAST("opened" AS FLOAT) AS "conversion" FROM ( SELECT @@ -42,14 +40,14 @@ With today’s Prisma ORM [v5.19.0](https://github.com/prisma/prisma/releases/ta ) AS "counts" ORDER BY "conversion" DESC ``` - + ```prisma model User { id String @id @default(uuid()) email String @unique trackingEvents TrackingEvent[] } - + model TrackingEvent { id String @id @default(uuid()) timestamp DateTime @default(now()) @@ -59,15 +57,14 @@ With today’s Prisma ORM [v5.19.0](https://github.com/prisma/prisma/releases/ta user User @relation(fields: [userId], references: [id]) } ``` - - You can also create SQL queries with arguments! - + + You can also create SQL queries with arguments! ```sql -- prisma/sql/conversionByVariantByVersion.sql - -- note that this syntax is for PostgreSQL. - -- Argument syntax will depend on your database engine. - + -- note that this syntax is for PostgreSQL. + -- Argument syntax will depend on your database engine. + SELECT "variant", CAST("checked_out" AS FLOAT) / CAST("opened" AS FLOAT) AS "conversion" FROM ( SELECT @@ -75,57 +72,57 @@ With today’s Prisma ORM [v5.19.0](https://github.com/prisma/prisma/releases/ta COUNT(*) FILTER (WHERE "type"='PageOpened') AS "opened", COUNT(*) FILTER (WHERE "type"='CheckedOut') AS "checked_out" FROM "TrackingEvent" - WHERE version = $1 + WHERE version = $1 GROUP BY "variant" ) AS "counts" ORDER BY "conversion" DESC ``` - + ```prisma model User { id String @id @default(uuid()) email String @unique trackingEvents TrackingEvent[] } - + model TrackingEvent { id String @id @default(uuid()) timestamp DateTime @default(now()) userId String type String variant String - version Int + version Int user User @relation(fields: [userId], references: [id]) } ``` - - -2. Generate query functions by using the `--sql` flag on `prisma generate`: -``` + +2. Generate query functions by using the `--sql` flag on `prisma generate`: + +```` npx prisma generate --sql ``` - + 3. Import the query function from `@prisma/client/sql` … - + ```typescript import { PrismaClient } from '@prisma/client' import { conversionByVariant } from '@prisma/client/sql' ``` - + … and call it inside the new `$queryRawTyped` function to get fully typed results 😎 - + ```typescript // `result` is fully typed! const result = await prisma.$queryRawTyped(conversionByVariant()) ``` - + If your SQL query has arguments, they are provided to the query function passed to `$queryRawTyped` - + ```typescript // only give me conversion results from TrackingEvent version 5 const result = await prisma.$queryRawTyped(conversionByVariantByVersion(5)) ``` - + The Prisma Client API together with TypedSQL provides the best experience for both CRUD operations and highly complex queries. With this addition, we hope you will never have to touch a SQL query builder again! @@ -148,7 +145,7 @@ If you’ve written raw SQL in a TypeScript project before, you likely know it d ### Application developers should care about _data_ – not SQL -At Prisma, we strongly believe that application developers should care about _data_ – not SQL. +At Prisma, we strongly believe that application developers should care about _data_ – not SQL. The majority of queries a typical application developer writes uses a fairly limited set of features, typically related to common CRUD operations, such as _pagination_, _filters_ or _nested queries_. @@ -178,33 +175,32 @@ model Post { author User @relation(fields: [authorId], references: [id]) authorId Int } -``` -Using the Prisma CLI, you can then generate a (customizable) SQL migration and run the migration against your database. Once the schema has been mapped to your database, you can query it with Prisma Client: - +```` +Using the Prisma CLI, you can then generate a (customizable) SQL migration and run the migration against your database. Once the schema has been mapped to your database, you can query it with Prisma Client: ```typescript // Create new user with posts await prisma.user.create({ data: { - name: 'Alice', - email: 'alice@prisma.io', + name: "Alice", + email: "alice@prisma.io", posts: { - create: { title: 'Hello World' }, + create: { title: "Hello World" }, }, }, -}) +}); ``` + ```typescript // Query users with posts await prisma.user.findMany({ include: { posts: true, }, -}) +}); ``` - ## Escape hatch: Dropping down to raw SQL While we believe that this kind of higher-level abstraction makes developers more productive, we have seen that many projects require the option to write raw SQL. This typically happens when: @@ -212,7 +208,7 @@ While we believe that this kind of higher-level abstraction makes developers mor - the Prisma Client API isn’t flexible enough to express a certain query. - a query needs to be optimized for speed. -In these cases, Prisma ORM offers an escape hatch for raw SQL by using the `$queryRaw` method of Prisma Client: +In these cases, Prisma ORM offers an escape hatch for raw SQL by using the `$queryRaw` method of Prisma Client: ```typescript const result = await prisma.$queryRaw` @@ -226,11 +222,12 @@ FROM ( GROUP BY "variant" ) AS "counts" ORDER BY "conversion" DESC -` +`; ``` + The main problem with this approach is that this query isn’t type-safe. If the developer wants to enjoy the type safety benefits they get from the standard Prisma Client API, they need to manually write the return types of this query, which can be cumbersome and time-consuming. Another problem is that these manually defined types don’t auto-update with schema changes, which introduces another possibility for error. -While there are ways to improve the DX using Prisma ORM’s raw queries, e.g. by using the [Kysely query builder extension for Prisma Cient](https://github.com/eoin-obrien/prisma-extension-kysely) or [SafeQL](https://safeql.dev/compatibility/prisma.html), we wanted to address this problem in a native way. +While there are ways to improve the DX using Prisma ORM’s raw queries, e.g. by using the [Kysely query builder extension for Prisma Cient](https://github.com/eoin-obrien/prisma-extension-kysely) or [SafeQL](https://safeql.dev/compatibility/prisma.html), we wanted to address this problem in a native way. ## New in Prisma ORM: TypedSQL 🎉 @@ -241,7 +238,7 @@ With TypedSQL, Prisma ORM now gives you the best of both worlds: - A higher-level abstraction that makes developers productive and can serve the majority of queries in a project. - A delightful and type-safe escape hatch for when you need to craft SQL directly. -It also gives development teams, where individual developers have different preferences, the option to choose their favorite approach: Do you have an Engineer on the team who’s a die-hard SQL fan but also some that wouldn’t touch SQL with a ten-foot pole? +It also gives development teams, where individual developers have different preferences, the option to choose their favorite approach: Do you have an Engineer on the team who’s a die-hard SQL fan but also some that wouldn’t touch SQL with a ten-foot pole? Prisma ORM now gives both groups what they want without sacrificing DX or flexibility! @@ -249,7 +246,7 @@ Prisma ORM now gives both groups what they want without sacrificing DX or flexib TypedSQL is your new companion whenever you would have resorted to using `$queryRaw` in the past. -We see TypedSQL as the evolution of SQL query builders, giving developers even more flexibility in their database queries because it removes all abstractions. +We see TypedSQL as the evolution of SQL query builders, giving developers even more flexibility in their database queries because it removes all abstractions.
[Try TypedSQL](https://pris.ly/typedsql-example) diff --git a/apps/blog/content/blog/announcing-upcoming-course-8s41wdqrlgc7/index.mdx b/apps/blog/content/blog/announcing-upcoming-course-8s41wdqrlgc7/index.mdx index 11154da246..7aa1431461 100644 --- a/apps/blog/content/blog/announcing-upcoming-course-8s41wdqrlgc7/index.mdx +++ b/apps/blog/content/blog/announcing-upcoming-course-8s41wdqrlgc7/index.mdx @@ -14,7 +14,7 @@ tags: --- We're thrilled to announce that we're working on a new course where you'll learn how to build a fullstack app using - Next.js, GraphQL, TypeScript, and Prisma! +Next.js, GraphQL, TypeScript, and Prisma! In this course, you'll learn how to build "Awesome Links", a fullstack app where users can browse through a list of curated links and bookmark their favorite ones. @@ -57,4 +57,3 @@ If you want to be notified when new lessons come out, you can [subscribe by usin
[Subscribe](https://mailchi.mp/354ea3b3ccc7/5znd7xz8z5) - diff --git a/apps/blog/content/blog/aws-marketplace/index.mdx b/apps/blog/content/blog/aws-marketplace/index.mdx index 602e233494..63e30ed3f8 100644 --- a/apps/blog/content/blog/aws-marketplace/index.mdx +++ b/apps/blog/content/blog/aws-marketplace/index.mdx @@ -39,34 +39,45 @@ As we continue to innovate and expand our offerings, [availability on AWS Market ### Frequently Asked Questions #### How do I switch to AWS billing if I am already a customer? + **A:** Please send us a message at [support@prisma.io](mailto:support@prisma.io) and we will help with the switch. - + #### What are the benefits of purchasing through AWS Marketplace? + **A:** Purchasing through AWS Marketplace allows you to consolidate your billing with other AWS services, potentially simplify procurement processes, and may offer tax benefits depending on your location and business structure. #### Will I receive the same level of support if I purchase through AWS Marketplace? + **A:** Yes, you will receive the same level of support and access to all features as customers who purchase directly from Prisma. #### Is there a difference in features or functionality when purchasing through AWS Marketplace? + **A:** No, you will have access to the same features and functionality as if you had purchased directly from Prisma. #### How does pricing compare between AWS Marketplace and direct purchase? + **A:** The pricing for our products on AWS Marketplace is consistent with our direct pricing. However, because you are paying annually, there’s a 10% discount built into the annual pricing and for ongoing usage, if you are on the Pro plan, you get the Business plan usage pricing. #### Can I purchase Prisma products through AWS Marketplace if I'm not in the United States? + **A:** AWS Marketplace is available in many countries. Please check AWS Marketplace availability in your region or contact our sales team for more information. #### How do I manage my Prisma subscription after purchasing through AWS Marketplace? + **A:** While billing will be handled through AWS, you will still manage your Prisma account and services through the Prisma Data Platform interface. #### Which Prisma Data Platform plans are available on AWS Marketplace? + **A:** You can purchase the Pro and Business plans on AWS Marketplace as public offers. If you are interested in Private offers, please follow the outlined process within AWS Marketplace. Note that even though the Pro and Business plans are annual on AWS Marketplace, usage is still billed on a monthly basis. #### Do you offer a free trial through AWS Marketplace? + **A:** There is no free trial when purchasing through AWS Marketplace, but you can still do a free trial by signing up directly on Prisma Data Platform and then elect to pay via AWS Marketplace once your testing is over. #### Can I upgrade or downgrade my plan once I have subscribed? + **A:** Since you will make an annual purchase, downgrading is not possible automatically. However, we are more than happy to help through the process. Just ping us at [support@prisma.io](mailto:support@prisma.io). #### Do I need to host anything on AWS to make the purchase through AWS Marketplace? + **A:** No, only billing will route through AWS Marketplace. You will still manage your account on the Prisma Data Platform and all resources are deployed and managed automatically by the Prisma Data Platform. No additional infrastructure is required to be set up by you. diff --git a/apps/blog/content/blog/backend-prisma-typescript-orm-with-postgresql-auth-mngp1ps7kip4/index.mdx b/apps/blog/content/blog/backend-prisma-typescript-orm-with-postgresql-auth-mngp1ps7kip4/index.mdx index 3a45e544c9..83e2ca6e5d 100644 --- a/apps/blog/content/blog/backend-prisma-typescript-orm-with-postgresql-auth-mngp1ps7kip4/index.mdx +++ b/apps/blog/content/blog/backend-prisma-typescript-orm-with-postgresql-auth-mngp1ps7kip4/index.mdx @@ -27,18 +27,18 @@ The recording of the live stream is available above and covers the same ground a The series will focus on the role of the database in every aspect of backend development covering: -| Topic | Part | -| ------------------------------ | ------------------------------------------------------------------- | -| Data Modeling | [Part 1](https://www.prisma.io/blog/backend-prisma-typescript-orm-with-postgresql-data-modeling-tsjs1ps7kip1) | -| CRUD | [Part 1](https://www.prisma.io/blog/backend-prisma-typescript-orm-with-postgresql-data-modeling-tsjs1ps7kip1) | -| Aggregations | [Part 1](https://www.prisma.io/blog/backend-prisma-typescript-orm-with-postgresql-data-modeling-tsjs1ps7kip1) | +| Topic | Part | +| ------------------------------ | ------------------------------------------------------------------------------------------------------------------- | +| Data Modeling | [Part 1](https://www.prisma.io/blog/backend-prisma-typescript-orm-with-postgresql-data-modeling-tsjs1ps7kip1) | +| CRUD | [Part 1](https://www.prisma.io/blog/backend-prisma-typescript-orm-with-postgresql-data-modeling-tsjs1ps7kip1) | +| Aggregations | [Part 1](https://www.prisma.io/blog/backend-prisma-typescript-orm-with-postgresql-data-modeling-tsjs1ps7kip1) | | REST API layer | [Part 2](https://www.prisma.io/blog/backend-prisma-typescript-orm-with-postgresql-rest-api-validation-dcba1ps7kip3) | | Validation | [Part 2](https://www.prisma.io/blog/backend-prisma-typescript-orm-with-postgresql-rest-api-validation-dcba1ps7kip3) | | Testing | [Part 2](https://www.prisma.io/blog/backend-prisma-typescript-orm-with-postgresql-rest-api-validation-dcba1ps7kip3) | -| Passwordless Authentication | Part 3 (current) | -| Authorization | Part 3 (current) | -| Integration with external APIs | Part 3 (current) | -| Deployment | Coming up | +| Passwordless Authentication | Part 3 (current) | +| Authorization | Part 3 (current) | +| Integration with external APIs | Part 3 (current) | +| Deployment | Coming up | ### What you will learn today @@ -85,11 +85,13 @@ Once you [sign up](https://signup.sendgrid.com/), go to [API Keys](https://app.s The source code for the series can be found on [GitHub](https://github.com/2color/real-world-grading-app). To get started, clone the repository and install the dependencies: + ``` git clone -b part-3 git@github.com:2color/real-world-grading-app.git cd real-world-grading-app npm install ``` + > **Note:** By checking out the `part-3` branch you'll be able to follow the article from the same starting point. ## Start PostgreSQL @@ -99,6 +101,7 @@ To start PostgreSQL, run the following command from the `real-world-grading-app` ```sh docker-compose up -d ``` + > **Note:** Docker will use the [`docker-compose.yml`](https://github.com/2color/real-world-grading-app/blob/21de326008776144ced60427a055c9fc54a32840/docker-compose.yml) file to start the PostgreSQL container. ## Authentication and authorization concepts @@ -170,11 +173,13 @@ With this authentication strategy, a single endpoint handles both logging in and The standard defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. A JWT token contains three parts which are encoded with Base64: _header_, _payload_, and _signature_ and looks as follows (the parts are separated with a `.`): + ``` eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. eyJ0b2tlbklkIjo5fQ. FkKMzLobPl_MaQHB7hRG3nZQZ-ME4lRaanGJVnLMa84 ``` + > Note: Base64 is another way to represent data. It doesn't involve any encryption If you use Base64 to decode the header and payload from above you will get the following: @@ -235,6 +240,7 @@ model User { + API +} ``` + Let's go over the changes introduced: - Enable the [`connectOrCreate`](https://www.prisma.io/docs/orm/reference/prisma-client-reference#connectorcreate) and [`transactionApi`](https://www.prisma.io/docs/orm/prisma-client/queries/transactions#the-transaction-api) preview features. These will be used in the next steps. @@ -247,7 +253,9 @@ To migrate the database schema, create and run the migration as follows: ```npm npx prisma migrate dev --preview-feature --name "add-token" ``` + **Checkpoint:** You should see something like the following in the output: + ``` Prisma Migrate created and applied the following migration(s) from new schema changes: @@ -259,6 +267,7 @@ migrations/ Everything is now in sync. ``` + > **Note:** Running the `prisma migrate dev` command will also generate Prisma Client by default. ## Add email sending functionality @@ -272,6 +281,7 @@ The article will use SendGrid and the `@sendgrid/mail` npm package for easy inte ```npm npm install --save @sendgrid/mail ``` + ### Creating the email plugin Create a new file named `email.ts` in the `src/plugins/` folder: @@ -279,67 +289,70 @@ Create a new file named `email.ts` in the `src/plugins/` folder: ```sh touch src/plugins/email.ts ``` + And add the following to the file: ```ts -import Hapi from '@hapi/hapi' -import Joi from '@hapi/joi' -import Boom from '@hapi/boom' -import sendgrid from '@sendgrid/mail' +import Hapi from "@hapi/hapi"; +import Joi from "@hapi/joi"; +import Boom from "@hapi/boom"; +import sendgrid from "@sendgrid/mail"; // Module augmentation to add shared application state // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/33809#issuecomment-472103564 -declare module '@hapi/hapi' { +declare module "@hapi/hapi" { interface ServerApplicationState { - sendEmailToken(email: string, token: string): Promise + sendEmailToken(email: string, token: string): Promise; } } const emailPlugin = { - name: 'app/email', - register: async function(server: Hapi.Server) { + name: "app/email", + register: async function (server: Hapi.Server) { if (!process.env.SENDGRID_API_KEY) { console.log( `The SENDGRID_API_KEY env var must be set, otherwise the API won't be able to send emails.`, `Using debug mode which logs the email tokens instead.`, - ) - server.app.sendEmailToken = debugSendEmailToken + ); + server.app.sendEmailToken = debugSendEmailToken; } else { - sendgrid.setApiKey(process.env.SENDGRID_API_KEY) - server.app.sendEmailToken = sendEmailToken + sendgrid.setApiKey(process.env.SENDGRID_API_KEY); + server.app.sendEmailToken = sendEmailToken; } }, -} +}; -export default emailPlugin +export default emailPlugin; async function sendEmailToken(email: string, token: string) { const msg = { to: email, - from: 'EMAIL_ADDRESS_CONFIGURED_IN_SEND_GRID@email.com', - subject: 'Login token for the modern backend API', + from: "EMAIL_ADDRESS_CONFIGURED_IN_SEND_GRID@email.com", + subject: "Login token for the modern backend API", text: `The login token for the API is: ${token}`, - } + }; - await sendgrid.send(msg) + await sendgrid.send(msg); } async function debugSendEmailToken(email: string, token: string) { - console.log(`email token for ${email}: ${token} `) + console.log(`email token for ${email}: ${token} `); } ``` + The plugin will expose the `sendEmailToken` function on the `server.app` object, which is accessible throughout your route handlers. It will use the `SENDGRID_API_KEY` environment variable, which you will set in production using the key from the SendGrid console. During development, you can leave it unset, and the token will be logged instead of being sent via email. Lastly, register the plugin in `server.ts`: ```ts -import emailPlugin from './plugins/email' +import emailPlugin from "./plugins/email"; await server.register([ // ... existing plugins emailPlugin, -]) +]); ``` + ## Adding authentication with Hapi To implement authentication you will begin by defining the `/login` and `/register` routes, which will handle the creation of a user and token in the database, sending the email token, verifying the email, and generating a JWT authentication token. It's worth noting that the two endpoints will handle the authentication process, but they will not secure the API. @@ -358,6 +371,7 @@ Begin by adding the following dependencies to your project: npm install --save hapi-auth-jwt2@10.1.0 jsonwebtoken@8.5.1 npm install --save-dev @types/jsonwebtoken@8.5.0 ``` + ### Creating the auth plugin Next, you will create an auth plugin to encapsulate the authentication logic. @@ -367,22 +381,24 @@ Create a new file named `auth.ts` in the `src/plugins/` folder: ```sh touch src/plugins/auth.ts ``` + And add the following to the file: ```ts -import Hapi from '@hapi/hapi' -import { TokenType, UserRole } from '@prisma/client' +import Hapi from "@hapi/hapi"; +import { TokenType, UserRole } from "@prisma/client"; const authPlugin: Hapi.Plugin = { - name: 'app/auth', - dependencies: ['prisma', 'hapi-auth-jwt2', 'app/email'], - register: async function(server: Hapi.Server) { + name: "app/auth", + dependencies: ["prisma", "hapi-auth-jwt2", "app/email"], + register: async function (server: Hapi.Server) { // TODO: Add the authentication strategy }, -} +}; -export default plugin +export default plugin; ``` + > **Note:** The auth plugin defines dependencies on the `prisma`, `hapi-auth-jwt2`, and `app/email` plugins. The prisma plugin was defined in part 2 of the series and will be used to access Prisma Client. The `hapi-auth-jwt2` plugin defines the `jwt` authentication scheme, which you will use to define the authentication the strategy. Lastly, the `app/email` will ensure you can access the `sendEmailToken` function. ### Defining the login endpoint @@ -393,44 +409,43 @@ In the `register` function of `authPlugin`, define a new login route as follows: server.route([ // Endpoint to login or register and to send the short-lived token { - method: 'POST', - path: '/login', + method: "POST", + path: "/login", handler: loginHandler, options: { auth: false, validate: { payload: Joi.object({ - email: Joi.string() - .email() - .required(), + email: Joi.string().email().required(), }), }, }, }, -]) +]); ``` + > **Note:** `options.auth` is set to false so that the endpoint will remain open once you set the default authentication strategy which will by default require authentication for all routes that don't disable it explicitly. Outside the register function of the plugin, add the following: ```ts -const EMAIL_TOKEN_EXPIRATION_MINUTES = 10 +const EMAIL_TOKEN_EXPIRATION_MINUTES = 10; interface LoginInput { - email: string + email: string; } async function loginHandler(request: Hapi.Request, h: Hapi.ResponseToolkit) { // 👇 get prisma and the sendEmailToken from shared application state - const { prisma, sendEmailToken } = request.server.app + const { prisma, sendEmailToken } = request.server.app; // 👇 get the email from the request payload - const { email } = request.payload as LoginInput + const { email } = request.payload as LoginInput; // 👇 generate an alphanumeric token - const emailToken = generateEmailToken() + const emailToken = generateEmailToken(); // 👇 create a date object for the email token expiration const tokenExpiration = add(new Date(), { minutes: EMAIL_TOKEN_EXPIRATION_MINUTES, - }) + }); try { // 👇 create a short lived token and update user or create if they don't exist @@ -450,21 +465,22 @@ async function loginHandler(request: Hapi.Request, h: Hapi.ResponseToolkit) { }, }, }, - }) + }); // 👇 send the email token - await sendEmailToken(email, emailToken) - return h.response().code(200) + await sendEmailToken(email, emailToken); + return h.response().code(200); } catch (error) { - return Boom.badImplementation(error.message) + return Boom.badImplementation(error.message); } } // Generate a random 8 digit number as the email token function generateEmailToken(): string { - return Math.floor(10000000 + Math.random() * 90000000).toString() + return Math.floor(10000000 + Math.random() * 90000000).toString(); } ``` + `loginHandler` does the following: - The email is taken from the request payload @@ -475,15 +491,16 @@ function generateEmailToken(): string { Finally, register the plugin in `server.ts`: ```ts -import hapiAuthJWT from 'hapi-auth-jwt2' -import authPlugin from './plugins/auth' +import hapiAuthJWT from "hapi-auth-jwt2"; +import authPlugin from "./plugins/auth"; await server.register([ // ... existing plugins hapiAuthJWT, authPlugin, -]) +]); ``` + **Checkpoint:** 1. Start the server with `npm run dev` @@ -497,47 +514,43 @@ Begin by adding the following route declaration to the `authPlugin`: ```ts server.route({ - method: 'POST', - path: '/authenticate', + method: "POST", + path: "/authenticate", handler: authenticateHandler, options: { auth: false, validate: { payload: Joi.object({ - email: Joi.string() - .email() - .required(), + email: Joi.string().email().required(), emailToken: Joi.string().required(), }), }, }, -}) +}); ``` + The route requires both the `email` and the `emailToken`. Since only the legitimate user attempting to login will know both, guessing both the `email` and `emailToken` becomes more difficult, thereby reducing the risk of brute force attacks which guess the eight-digit number. Next, add the following to `auth.ts`: ```ts // Load the JWT secret from environment variables or default -const JWT_SECRET = process.env.JWT_SECRET || 'SUPER_SECRET_JWT_SECRET' +const JWT_SECRET = process.env.JWT_SECRET || "SUPER_SECRET_JWT_SECRET"; -const JWT_ALGORITHM = 'HS256' +const JWT_ALGORITHM = "HS256"; -const AUTHENTICATION_TOKEN_EXPIRATION_HOURS = 12 +const AUTHENTICATION_TOKEN_EXPIRATION_HOURS = 12; interface AuthenticateInput { - email: string - emailToken: string + email: string; + emailToken: string; } -async function authenticateHandler( - request: Hapi.Request, - h: Hapi.ResponseToolkit, -) { +async function authenticateHandler(request: Hapi.Request, h: Hapi.ResponseToolkit) { // 👇 get prisma from shared application state - const { prisma } = request.server.app + const { prisma } = request.server.app; // 👇 get the email and emailToken from the request payload - const { email, emailToken } = request.payload as AuthenticateInput + const { email, emailToken } = request.payload as AuthenticateInput; try { // Get short lived email token @@ -548,23 +561,23 @@ async function authenticateHandler( include: { user: true, }, - }) + }); if (!fetchedEmailToken?.valid) { // If the token doesn't exist or is not valid, return 401 unauthorized - return Boom.unauthorized() + return Boom.unauthorized(); } if (fetchedEmailToken.expiration < new Date()) { // If the token has expired, return 401 unauthorized - return Boom.unauthorized('Token expired') + return Boom.unauthorized("Token expired"); } // If token matches the user email passed in the payload, generate long lived API token if (fetchedEmailToken?.user?.email === email) { const tokenExpiration = add(new Date(), { hours: AUTHENTICATION_TOKEN_EXPIRATION_HOURS, - }) + }); // Persist token in DB so it's stateful const createdToken = await prisma.token.create({ data: { @@ -576,7 +589,7 @@ async function authenticateHandler( }, }, }, - }) + }); // Invalidate the email token after it's been used await prisma.token.update({ @@ -586,28 +599,29 @@ async function authenticateHandler( data: { valid: false, }, - }) + }); - const authToken = generateAuthToken(createdToken.id) - return h.response().code(200).header('Authorization', authToken) + const authToken = generateAuthToken(createdToken.id); + return h.response().code(200).header("Authorization", authToken); } else { - return Boom.unauthorized() + return Boom.unauthorized(); } } catch (error) { - return Boom.badImplementation(error.message) + return Boom.badImplementation(error.message); } } // Generate a signed JWT token with the tokenId in the payload function generateAuthToken(tokenId: number): string { - const jwtPayload = { tokenId } + const jwtPayload = { tokenId }; return jwt.sign(jwtPayload, JWT_SECRET, { algorithm: JWT_ALGORITHM, noTimestamp: true, - }) + }); } ``` + > **Note:** The environment variable `JWT_SECRET` can be generated by running the following command: `node -e "console.log(require('crypto').randomBytes(256).toString('base64'));"`. This should always be set in production environments. The handler fetches the email token from the database, ensures it's valid, creates a new API token in the database, generates a JWT token (with a reference to the token in the database), invalidates the email token, and returns the token in the `Authorization` header. @@ -627,27 +641,29 @@ To define the authentication strategy, add the following to `auth.ts`: ```ts // This strategy will be used across the application to secure routes -export const API_AUTH_STATEGY = 'API' +export const API_AUTH_STATEGY = "API"; ``` + Inside the `authPlugin.register` function add the following: ```ts // Define the authentication strategy which uses the `jwt` authentication scheme -server.auth.strategy(API_AUTH_STATEGY, 'jwt', { +server.auth.strategy(API_AUTH_STATEGY, "jwt", { key: JWT_SECRET, verifyOptions: { algorithms: [JWT_ALGORITHM] }, validate: validateAPIToken, -}) +}); // Set the default authentication strategy for API routes, unless explicitly disabled -server.auth.default(API_AUTH_STATEGY) +server.auth.default(API_AUTH_STATEGY); ``` + Lastly, add the `validateAPIToken` function: ```ts const apiTokenSchema = Joi.object({ tokenId: Joi.number().integer().required(), -}) +}); // Function will be called on every request using the auth strategy const validateAPIToken = async ( @@ -655,14 +671,14 @@ const validateAPIToken = async ( request: Hapi.Request, h: Hapi.ResponseToolkit, ) => { - const { prisma } = request.server.app - const { tokenId } = decoded + const { prisma } = request.server.app; + const { tokenId } = decoded; // Validate the token payload adheres to the schema - const { error } = apiTokenSchema.validate(decoded) + const { error } = apiTokenSchema.validate(decoded); if (error) { - request.log(['error', 'auth'], `API token error: ${error.message}`) - return { isValid: false } + request.log(["error", "auth"], `API token error: ${error.message}`); + return { isValid: false }; } try { @@ -674,16 +690,16 @@ const validateAPIToken = async ( include: { user: true, }, - }) + }); // Check if token could be found in database and is valid if (!fetchedToken || !fetchedToken?.valid) { - return { isValid: false, errorMessage: 'Invalid Token' } + return { isValid: false, errorMessage: "Invalid Token" }; } // Check token expiration if (fetchedToken.expiration < new Date()) { - return { isValid: false, errorMessage: 'Token expired' } + return { isValid: false, errorMessage: "Token expired" }; } // Get all the courses that the user is the teacher of @@ -695,7 +711,7 @@ const validateAPIToken = async ( select: { courseId: true, }, - }) + }); // The token is valid. Make the `userId`, `isAdmin`, and `teacherOf` to `credentials` which is available in route handlers via `request.auth.credentials` return { @@ -707,13 +723,14 @@ const validateAPIToken = async ( // convert teacherOf from an array of objects to an array of numbers teacherOf: teacherOf.map(({ courseId }) => courseId), }, - } + }; } catch (error) { - request.log(['error', 'auth', 'db'], error) - return { isValid: false } + request.log(["error", "auth", "db"], error); + return { isValid: false }; } -} +}; ``` + The `validateAPIToken` function will get called before every route that uses the `API_AUTH_STATEGY` (which you've set as the default in the previous step). The purpose of the `validateAPIToken` function is to **determine whether to allow the request to proceed**. This is done with the return object, which contains `isValid` and `credentials`: @@ -803,24 +820,25 @@ To add a pre-function to verify the authorization rule in the `GET /users/{userI // Pre-function to check if the authenticated user matches the requested user export async function isRequestedUserOrAdmin(request: Hapi.Request, h: Hapi.ResponseToolkit) { // 👇 userId and isAdmin are populated by the `validateAPIToken` function - const { userId, isAdmin } = request.auth.credentials + const { userId, isAdmin } = request.auth.credentials; if (isAdmin) { // If the user is an admin allow - return h.continue + return h.continue; } - const requestedUserId = parseInt(request.params.userId, 10) + const requestedUserId = parseInt(request.params.userId, 10); // 👇 Check that the requested userId matches the authenticated userId if (requestedUserId === userId) { - return h.continue + return h.continue; } // The authenticated user is not authorized - throw Boom.forbidden() + throw Boom.forbidden(); } ``` + Then add the pre option to the route definition in `src/plugins/user.ts` as follows: ```json @@ -843,6 +861,7 @@ diff }, } ``` + The pre-function will now be called before `getUserHandler` and only authorize access to admins or to users requesting their own userId. > **Note:** In the previous part, you've defined the default authentication strategy, so defining `options.auth` is not strictly required. But it's good practice to define the authentication requirements for every route explicitly. @@ -851,18 +870,24 @@ The pre-function will now be called before `getUserHandler` and only authorize a 1. Start the server with `npm run dev` 1. Run the `seed-users` script to create a test user and test admin: `npm run seed-users`. You should get a result similar to this: + ``` Created test user id: 1 | email: test@prisma.io Created test admin id: 2 | email: test-admin@prisma.io - ``` +``` + 1. Login as `test@prisma.io` by making a call to the `POST /login` endpoint as follows: + ``` curl --header "Content-Type: application/json" --request POST --data '{"email":"test@prisma.io"}' localhost:3000/login - ``` +``` + 1. Take the logged token and call the `/authenticate` endpoint with curl: + ``` curl -v --header "Content-Type: application/json" --request POST --data '{"email":"test@prisma.io", "emailToken": "TOKEN_FROM_CONSOLE"}' localhost:3000/authenticate - ``` +``` + 1. The response should have the `200` status and include an `Authorization` header which looks similar to this: `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbklkIjoyOH0.gJYk3f1RJVKvPh75FdElCHwMoe_ZCMZftTE1Em5PpMg` 1. Make a GET call to `/users/1` (where the number is the test user created in the first step of the checkpoint) with the `Authorization` header containing the token from the last checkpoint as follows: `curl -H "Authorization:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbklkIjoyOH0.gJYk3f1RJVKvPh75FdElCHwMoe_ZCMZftTE1Em5PpMg" localhost:3000/users/1` and the request should succeed and you should see the user profile. 1. Make another GET call to `/users/2` with the same authorization header: `curl -H "Authorization:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbklkIjoyOH0.gJYk3f1RJVKvPh75FdElCHwMoe_ZCMZftTE1Em5PpMg" localhost:3000/users/2`. This one should fail with a 403 forbidden error. @@ -891,28 +916,26 @@ Teachers should be able to update courses and create tests for courses they are Define the following pre-function in `auth-helpers.ts`: ```ts -export async function isTeacherOfCourseOrAdmin( - request: Hapi.Request, - h: Hapi.ResponseToolkit, -) { +export async function isTeacherOfCourseOrAdmin(request: Hapi.Request, h: Hapi.ResponseToolkit) { // 👇 isAdmin and teacherOf are populated by the `validateAPIToken` function - const { isAdmin, teacherOf } = request.auth.credentials + const { isAdmin, teacherOf } = request.auth.credentials; if (isAdmin) { // If the user is an admin allow - return h.continue + return h.continue; } - const courseId = parseInt(request.params.courseId, 10) + const courseId = parseInt(request.params.courseId, 10); // Verify that the authenticated user is a teacher of the requested course if (teacherOf?.includes(courseId)) { - return h.continue + return h.continue; } // If the user is not a teacher of the course, deny access - throw Boom.forbidden() + throw Boom.forbidden(); } ``` + The pre-function uses the `teacherOf` array that is fetched in `validateAPIToken` to check if the user is a teacher of the requested course. Add the `isTeacherOfCourseOrAdmin` as a pre-function to the following routes: @@ -931,6 +954,7 @@ options: { // ... other route options } ``` + You have now implemented two different authorization rules and added then as a pre-function to ten different routes in the backend. ## Updating the tests @@ -940,17 +964,18 @@ After implementing authentication and authorization in the REST API, the tests w For example, the `GET /users/{userId}` endpoint has the following test: ```ts -test('get user returns user', async () => { +test("get user returns user", async () => { const response = await server.inject({ - method: 'GET', + method: "GET", url: `/users/${userId}`, - }) - expect(response.statusCode).toEqual(200) - const user = JSON.parse(response.payload) + }); + expect(response.statusCode).toEqual(200); + const user = JSON.parse(response.payload); - expect(user.id).toBe(userId) -}) + expect(user.id).toBe(userId); +}); ``` + If you run this test now with `npm run test -- -t="get user returns user"` the test will fail. This is because the test when the request reaches the endpoint it does not meet its authentication requirements. With Hapi's [`server.inject`](https://hapi.dev/api?v=19.2.0#-await-serverinjectoptions) –that simulates an HTTP request to the server–, you can add an `auth` object with information about the authenticated user. The `auth` object sets the credentials object as they would in the `validateAPIToken` function in `src/plugins/auth.ts`, for example: ```ts @@ -974,22 +999,24 @@ test('get user returns user', async () => { expect(user.id).toBe(testUserCredentials.userId) }) ``` + The `credentials` object passed matches the `AuthCredentials` interface defined in `src/plugins/auth.ts`: ```ts interface AuthCredentials { - userId: number - tokenId: number - isAdmin: boolean - teacherOf: number[] + userId: number; + tokenId: number; + isAdmin: boolean; + teacherOf: number[]; } ``` + > **Note:** An interface in TypeScript is very similar to a type with some subtle differences. To learn more, checkout the [TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/advanced-types.html#interfaces-vs-type-aliases). For the test to pass, you will create a user directly with Prisma in the test and construct the `AuthCredentials` object as follows: ```ts -test('get user returns user', async () => { +test("get user returns user", async () => { const testUser = await server.app.prisma.user.create({ data: { email: `test-${Date.now()}@test.com`, @@ -1004,29 +1031,30 @@ test('get user returns user', async () => { include: { tokens: true, }, - }) + }); const testUserCredentials = { userId: testUser.id, tokenId: testUser.tokens[0].id, isAdmin: testUser.isAdmin, teacherOf: [], // empty array because no courses were created for the user - } + }; const response = await server.inject({ - method: 'GET', + method: "GET", url: `/users/${testUserCredentials.userId}`, auth: { strategy: API_AUTH_STATEGY, credentials: testUserCredentials, }, - }) - expect(response.statusCode).toEqual(200) - const user = JSON.parse(response.payload) + }); + expect(response.statusCode).toEqual(200); + const user = JSON.parse(response.payload); - expect(user.id).toBe(testUserCredentials.userId) -}) + expect(user.id).toBe(testUserCredentials.userId); +}); ``` + **Checkpoint:** Run `npm run test -- -t="get user returns user"` to verify that the test passes. At this point, you've fixed one test, but what about the others? Since creating the credentials object will be required in most of the tests, you can abstract it into a separate `test-helpers.ts` module: @@ -1059,16 +1087,17 @@ export const createUserCredentials = async ( }, }, }, - }) + }); return { userId: testUser.id, tokenId: testUser.tokens[0].id, isAdmin: testUser.isAdmin, teacherOf: testUser.courses?.map(({ courseId }) => courseId), - } -} + }; +}; ``` + As a next step, write a test that that verifies the authorization rule allowing admins to fetch different user accounts with the `GET /users/{userId}` endpoint. ## Summary and next steps @@ -1096,4 +1125,3 @@ You can find the full source code on [GitHub](https://github.com/2color/real-wor While Prisma aims to make working with relational databases easy, it's useful to understand the underlying database and authentication principles. If you have questions, feel free to reach out on [Twitter](https://twitter.com/daniel2color). - diff --git a/apps/blog/content/blog/backend-prisma-typescript-orm-with-postgresql-data-modeling-tsjs1ps7kip1/index.mdx b/apps/blog/content/blog/backend-prisma-typescript-orm-with-postgresql-data-modeling-tsjs1ps7kip1/index.mdx index 1f5deab3a7..fb31783541 100644 --- a/apps/blog/content/blog/backend-prisma-typescript-orm-with-postgresql-data-modeling-tsjs1ps7kip1/index.mdx +++ b/apps/blog/content/blog/backend-prisma-typescript-orm-with-postgresql-data-modeling-tsjs1ps7kip1/index.mdx @@ -74,11 +74,13 @@ If you're using Visual Studio Code, the [Prisma extension](https://marketplace.v The source code for the series can be found on [GitHub](https://github.com/2color/real-world-grading-app). To get started, clone the repository and install the dependencies: + ``` git clone -b part-1 git@github.com:2color/real-world-grading-app.git cd real-world-grading-app npm install ``` + > **Note:** By checking out the `part-1` branch you'll be able to follow the article from the same starting point. ## Start PostgreSQL @@ -88,6 +90,7 @@ To start PostgreSQL, run the following command from the `real-world-grading-app` ```sh docker-compose up -d ``` + > **Note:** Docker will use the [`docker-compose.yml`](https://github.com/2color/real-world-grading-app/blob/21de326008776144ced60427a055c9fc54a32840/docker-compose.yml) file to start the PostgreSQL container. ## Data model for a grading system for online courses @@ -151,14 +154,15 @@ model User { social Json? } ``` + Here you define a `User` model with several [fields](https://www.prisma.io/docs/concepts/components/prisma-schema/data-model#fields). Each field has a name followed by a type and optional field attributes. For example, the `id` field could be broken down as follows: -| Name | Type | Scalar vs Relation | Type modifier | Attributes | -| :---------- | :------- | :----------------- | :-------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------- | -| `id` | `Int` | Scalar | - | `@id` (denote the primary key) and `@default(autoincrement())` (set a default auto-increment value) | -| `email` | `String` | Scalar | - | `@unique` | -| `firstName` | `String` | Scalar | - | - | -| `lastName` | `String` | Scalar | - | - | +| Name | Type | Scalar vs Relation | Type modifier | Attributes | +| :---------- | :------- | :----------------- | :------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------- | +| `id` | `Int` | Scalar | - | `@id` (denote the primary key) and `@default(autoincrement())` (set a default auto-increment value) | +| `email` | `String` | Scalar | - | `@unique` | +| `firstName` | `String` | Scalar | - | - | +| `lastName` | `String` | Scalar | - | - | | `social` | `Json` | Scalar | `?` ([optional](https://www.prisma.io/docs/concepts/components/prisma-schema/data-model#optional-vs-required)) | - | Prisma defines a [set of data types](https://www.prisma.io/docs/concepts/components/prisma-schema/data-model#scalar-types) that map to the native database types depending on the database used. @@ -195,6 +199,7 @@ model TestResult { result Int // Percentage precise to one decimal point represented as `result * 10^-1` } ``` + Each model has all the relevant fields while ignoring relations (which will be defined in the next step). ### Define relations @@ -219,6 +224,7 @@ model TestResult { result Int // Percentage precise to one decimal point represented result * 10^-1 } ``` + To define a one-to-many relation between the two models, add the following three fields: - `testId` field of type `Int` ([_relation scalar_](https://www.prisma.io/docs/concepts/components/prisma-schema/relations#annotated-relation-fields-and-relation-scalar-fields)) on the "many" side of the relation: `TestResult`. This field represents the _foreign key_ in the underlying database table. @@ -244,6 +250,7 @@ model TestResult { + test Test @relation(fields: [testId], references: [id]) // relation field } ``` + Relation fields like `test` and `testResults` can be identified by their value type pointing to another model, e.g. `Test` and `TestResult`. Their name will affect the way that relations are accessed programmatically with Prisma Client, however, they don't represent a real database column. ### Many-to-many relations @@ -269,6 +276,7 @@ model Course { courseDetails String? } ``` + To create an implicit many-to-many relation, define relation fields as lists on both sides of the relations: ```prisma @@ -289,6 +297,7 @@ model Course { + members User[] } ``` + With this, Prisma will create the relation table so the grading system can maintain the properties defined above: - A single course can have many associated users. @@ -336,6 +345,7 @@ model Course { + TEACHER +} ``` + Things to note about the `CourseEnrollment` model: - It uses the `UserRole` enum to denote whether a user is a student or a teacher of a course. @@ -421,6 +431,7 @@ enum UserRole { TEACHER } ``` + Note that `TestResult` has two relations to the `User` model: `student` and `gradedBy` to represent both the teacher who graded the test and the student who took the test. The `name` argument on the `@relation` attribute is necessary to [disambiguate the relation](https://www.prisma.io/docs/concepts/components/prisma-schema/relations#disambiguating-relations) when a single model has more than one relation to the same model. ## Migrating the database @@ -432,6 +443,7 @@ First, set the `DATABASE_URL` environment variable locally so that Prisma can co ```sh export DATABASE_URL="postgresql://prisma:prisma@127.0.0.1:5432/grading-app" ``` + > **Note**: The username and password for the local database are both defined as `prisma` in [`docker-compose.yml`](https://github.com/2color/real-world-grading-app/blob/21de326008776144ced60427a055c9fc54a32840/docker-compose.yml#L12-L13). To create and run a migration with Prisma Migrate, run the following command in your terminal: @@ -439,14 +451,16 @@ To create and run a migration with Prisma Migrate, run the following command in ```npm npx prisma migrate dev --preview-feature --skip-generate --name "init" ``` + The command will do two things: + - **Save the migration:** Prisma Migrate will take a snapshot of your schema and figure out the SQL necessary to carry out the migration. The migration file containing the SQL will be saved to `prisma/migrations` - **Run the migration:** Prisma Migrate will execute the SQL in the migration file to run the migration and alter (or create) the database schema > **Note:** Prisma Migrate is currently in [preview](https://www.prisma.io/docs/about/prisma/releases#preview) mode. This means that it is not recommended to use Prisma Migrate in production. - **Checkpoint:** You should see something like the following in the output: + ``` Prisma Migrate created and applied the following migration(s) from new schema changes: @@ -456,6 +470,7 @@ migrations/ Everything is now in sync. ``` + Congratulations, you have successfully designed the data model and created the database schema. In the next step, you will use Prisma Client to perform CRUD and aggregation queries against your database. ## Generating Prisma Client @@ -471,13 +486,16 @@ Generating Prisma Client, typically requires three steps: } ``` 1. Install the `@prisma/client` npm package + ``` npm install --save @prisma/client - ``` +``` + 1. Generate Prisma Client with the following command: + ``` npx prisma generate - ``` +``` **Checkpoint:** You should see the following in the output: `✔ Generated Prisma Client to ./node_modules/@prisma/client in 57ms` @@ -496,40 +514,45 @@ Begin by creating a user as follows in `main` function: ```ts const grace = await prisma.user.create({ data: { - email: 'grace@hey.com', - firstName: 'Grace', - lastName: 'Bell', + email: "grace@hey.com", + firstName: "Grace", + lastName: "Bell", social: { - facebook: 'gracebell', - twitter: 'therealgracebell', + facebook: "gracebell", + twitter: "therealgracebell", }, }, -}) +}); ``` + The operation will create a row in the _User_ table and return the created user (including the created `id`). It's worth noting that `user` will infer the type `User` which is defined in `@prisma/client`: ```ts export type User = { - id: number - email: string - firstName: string - lastName: string - social: JsonValue | null -} + id: number; + email: string; + firstName: string; + lastName: string; + social: JsonValue | null; +}; ``` + To execute the seed script and create the `User` record, you can use the [`seed` script in the `package.json`](https://github.com/2color/real-world-grading-app/blob/part-1/package.json#L25) as follows: + ``` npm run seed ``` + As you follow the next steps, you will run the seed script more than once. To avoid hitting unique constraint errors, you can delete the contents of the database in the beginning of the `main` functions as follows: ```ts -await prisma.testResult.deleteMany({}) -await prisma.courseEnrollment.deleteMany({}) -await prisma.test.deleteMany({}) -await prisma.user.deleteMany({}) -await prisma.course.deleteMany({}) +await prisma.testResult.deleteMany({}); +await prisma.courseEnrollment.deleteMany({}); +await prisma.test.deleteMany({}); +await prisma.user.deleteMany({}); +await prisma.course.deleteMany({}); ``` + > **Note:** These commands delete all rows in each database table. Use carefully and avoid this in production! ### Creating a course and related tests and users @@ -539,32 +562,33 @@ In this step, you will create a _course_ and use a nested write to create relate Add the following to the `main` function: ```ts -const weekFromNow = add(new Date(), { days: 7 }) -const twoWeekFromNow = add(new Date(), { days: 14 }) -const monthFromNow = add(new Date(), { days: 28 }) +const weekFromNow = add(new Date(), { days: 7 }); +const twoWeekFromNow = add(new Date(), { days: 14 }); +const monthFromNow = add(new Date(), { days: 28 }); const course = await prisma.course.create({ data: { - name: 'CRUD with Prisma', + name: "CRUD with Prisma", tests: { create: [ { date: weekFromNow, - name: 'First test', + name: "First test", }, { date: twoWeekFromNow, - name: 'Second test', + name: "Second test", }, { date: monthFromNow, - name: 'Final exam', + name: "Final exam", }, ], }, }, -}) +}); ``` + This will create a row in the `Course` table and three related rows in the `Tests` table (`Course` and `Tests` have a one-to-many relationship which allows this). What if you wanted to create a relation between the user created in the previous step to this course as a teacher? @@ -574,32 +598,32 @@ What if you wanted to create a relation between the user created in the previous This can be done as follows (adding to the query from the previous step): ```ts -const weekFromNow = add(new Date(), { days: 7 }) -const twoWeekFromNow = add(new Date(), { days: 14 }) -const monthFromNow = add(new Date(), { days: 28 }) +const weekFromNow = add(new Date(), { days: 7 }); +const twoWeekFromNow = add(new Date(), { days: 14 }); +const monthFromNow = add(new Date(), { days: 28 }); const course = await prisma.course.create({ data: { - name: 'CRUD with Prisma', + name: "CRUD with Prisma", tests: { create: [ { date: weekFromNow, - name: 'First test', + name: "First test", }, { date: twoWeekFromNow, - name: 'Second test', + name: "Second test", }, { date: monthFromNow, - name: 'Final exam', + name: "Final exam", }, ], }, members: { create: { - role: 'TEACHER', + role: "TEACHER", user: { connect: { email: grace.email, @@ -611,8 +635,9 @@ const course = await prisma.course.create({ include: { tests: true, }, -}) +}); ``` + > **Note:** the [`include`](https://www.prisma.io/docs/concepts/components/prisma-client/select-fields#include) argument allows you to fetch relations in the result. This will be useful in a later step to relate test results with tests When using nested writes (as with `members` and `tests`) there are two options: @@ -633,36 +658,37 @@ Add the following statements: ```ts const shakuntala = await prisma.user.create({ data: { - email: 'devi@prisma.io', - firstName: 'Shakuntala', - lastName: 'Devi', + email: "devi@prisma.io", + firstName: "Shakuntala", + lastName: "Devi", courses: { create: { - role: 'STUDENT', + role: "STUDENT", course: { connect: { id: course.id }, }, }, }, }, -}) +}); const david = await prisma.user.create({ data: { - email: 'david@prisma.io', - firstName: 'David', - lastName: 'Deutsch', + email: "david@prisma.io", + firstName: "David", + lastName: "Deutsch", courses: { create: { - role: 'STUDENT', + role: "STUDENT", course: { connect: { id: course.id }, }, }, }, }, -}) +}); ``` + ### Adding test results for the students Looking at the `TestResult` model, it has three relations: `student`, `gradedBy`, and `test`. To add test results for Shakuntala and David, you will use nested writes similarly to the previous steps. @@ -684,6 +710,7 @@ model TestResult { test Test @relation(fields: [testId], references: [id]) } ``` + Adding a single test result would look as follows: ```ts @@ -700,15 +727,16 @@ await prisma.testResult.create({ }, result: 950, }, -}) +}); ``` + To add a test result for both David and Shakuntala for each of the three tests, you can create a loop: ```ts -const testResultsDavid = [650, 900, 950] -const testResultsShakuntala = [800, 950, 910] +const testResultsDavid = [650, 900, 950]; +const testResultsShakuntala = [800, 950, 910]; -let counter = 0 +let counter = 0; for (const test of course.tests) { await prisma.testResult.create({ data: { @@ -723,7 +751,7 @@ for (const test of course.tests) { }, result: testResultsShakuntala[counter], }, - }) + }); await prisma.testResult.create({ data: { @@ -738,17 +766,20 @@ for (const test of course.tests) { }, result: testResultsDavid[counter], }, - }) + }); - counter++ + counter++; } ``` + Congratulations, if you have reached this point, you successfully created sample data for users, courses, tests, and test results in your database. To explore the data in the database, you can run [Prisma Studio](https://www.prisma.io/docs/concepts/components/prisma-studio). Prisma Studio is a visual editor for your database. To run Prisma Studio, run the following command in your terminal: + ``` npx prisma studio ``` + ## Aggregating the test results with Prisma Client Prisma Client allows you to perform aggregate operations on number fields (such as `Int` and `Float`) of a model. Aggregate operations compute a single result from a set of input values, i.e. multiple rows in a table. For example, calculating the _minimum_, _maximum_, and _average_ value of the `result` column over a set of `TestResult` rows. @@ -767,8 +798,8 @@ In this step, you will run two kinds of aggregate operations: max: { result: true }, min: { result: true }, count: true, - }) - console.log(`test: ${test.name} (id: ${test.id})`, results) + }); + console.log(`test: ${test.name} (id: ${test.id})`, results); } ``` @@ -807,8 +838,8 @@ In this step, you will run two kinds of aggregate operations: max: { result: true }, min: { result: true }, count: true, - }) - console.log(`David's results (email: ${david.email})`, davidAggregates) + }); + console.log(`David's results (email: ${david.email})`, davidAggregates); // Get aggregates for Shakuntala const shakuntalaAggregates = await prisma.testResult.aggregate({ @@ -819,8 +850,8 @@ In this step, you will run two kinds of aggregate operations: max: { result: true }, min: { result: true }, count: true, - }) - console.log(`Shakuntala's results (email: ${shakuntala.email})`, shakuntalaAggregates) + }); + console.log(`Shakuntala's results (email: ${shakuntala.email})`, shakuntalaAggregates); ``` This results in the following terminal output: diff --git a/apps/blog/content/blog/backend-prisma-typescript-orm-with-postgresql-deployment-bbba1ps7kip5/index.mdx b/apps/blog/content/blog/backend-prisma-typescript-orm-with-postgresql-deployment-bbba1ps7kip5/index.mdx index dab4075a39..3a87a6d19f 100644 --- a/apps/blog/content/blog/backend-prisma-typescript-orm-with-postgresql-deployment-bbba1ps7kip5/index.mdx +++ b/apps/blog/content/blog/backend-prisma-typescript-orm-with-postgresql-deployment-bbba1ps7kip5/index.mdx @@ -27,8 +27,8 @@ The recording of the live stream is available above and covers the same ground a The series will focus on the role of the database in every aspect of backend development covering: -| Topic | Part | -| ------------------------------ | ---------------------------------------------------------------------------------------------- | +| Topic | Part | +| ------------------------------ | ------------------------------------------------------------------------------------------------------------------- | | Data Modeling | [Part 1](https://www.prisma.io/blog/backend-prisma-typescript-orm-with-postgresql-data-modeling-tsjs1ps7kip1) | | CRUD | [Part 1](https://www.prisma.io/blog/backend-prisma-typescript-orm-with-postgresql-data-modeling-tsjs1ps7kip1) | | Aggregations | [Part 1](https://www.prisma.io/blog/backend-prisma-typescript-orm-with-postgresql-data-modeling-tsjs1ps7kip1) | @@ -38,8 +38,8 @@ The series will focus on the role of the database in every aspect of backend dev | Passwordless Authentication | [Part 3](https://www.prisma.io/blog/backend-prisma-typescript-orm-with-postgresql-auth-mngp1ps7kip4) | | Authorization | [Part 3](https://www.prisma.io/blog/backend-prisma-typescript-orm-with-postgresql-auth-mngp1ps7kip4) | | Integration with external APIs | [Part 3](https://www.prisma.io/blog/backend-prisma-typescript-orm-with-postgresql-auth-mngp1ps7kip4) | -| Continuous Integration | Part 4 (current) | -| Deployment | Part 4 (current) | +| Continuous Integration | Part 4 (current) | +| Deployment | Part 4 (current) | In the first article, you designed a [data model](/backend-prisma-typescript-orm-with-postgresql-data-modeling-tsjs1ps7kip1) for the problem domain and wrote a [seed script](https://github.com/2color/real-world-grading-app/blob/e84fc764df5901f3860225e28b844fb6ede6e632/src/seed.ts) that uses [Prisma Client](https://www.prisma.io/docs/concepts/components/prisma-client) to save data to the database. @@ -79,7 +79,6 @@ With continuous integration, the main building block is a pipeline. A pipeline i When working in a team where code changes are introduced using pull requests, CI servers would usually be configured to automatically run the pipeline for every pull request. - The [tests](https://github.com/2color/real-world-grading-app/tree/master/tests) you wrote in the previous steps work by simulating requests to the API's endpoints. Since the handlers for those endpoints interact with the database, you will need a PostgreSQL database with the backend's schema for the duration of the tests. In the next step, you will configure GitHub Actions to run a test database (for the duration of the CI run) and run the migrations so that the test database is in line with your Prisma schema. > **Note:** CI is only as good as the tests you wrote. If your test coverage is low, passing tests may create a false sense of confidence. @@ -129,7 +128,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: - node-version: '12.x' + node-version: "12.x" - run: npm ci # run the migration in the test database - run: npm run db:push @@ -151,6 +150,7 @@ jobs: heroku_app_name: ${{ secrets.HEROKU_APP_NAME }} heroku_email: ${{ secrets.HEROKU_EMAIL }} ``` + The `grading-app` workflow has two jobs: `test` and `deploy`. The **test** job will do the following: @@ -191,30 +191,39 @@ Now, when you push a commit to the repository, GitHub will run the workflow. ## Heroku CLI login Make sure you're logged in to Heroku with the CLI: + ``` heroku login ``` + ## Creating a Heroku app To deploy the backend application to Heroku, you need to create a Heroku app. Run the following command from the folder of the cloned repository: + ``` cd real-world-grading-app heroku apps:create YOUR_APP_NAME ``` + > **Note:** Use a unique name of your choice instead of `YOUR_APP_NAME`. **Checkpoint** The Heroku CLI should log that the app has been successfully created: + ``` Creating ⬢ YOUR_APP_NAME... done ``` + ## Provisioning a PostgreSQL database on Heroku Create the database with the following command: + ``` heroku addons:create heroku-postgresql:hobby-dev ``` + **Checkpoint**: To verify the database was created you should see the following: + ``` Creating heroku-postgresql:hobby-dev on ⬢ YOUR_APP_NAME... free Database has been created and is available @@ -222,6 +231,7 @@ Database has been created and is available ! data from another database with pg:copy Created postgresql-closed-86440 as DATABASE_URL ``` + > **Note:** Heroku will automatically set the `DATABASE_URL` environment variable for the application runtime. Prisma Client will use `DATABASE_URL` as it matches the environment variable configured in the [Prisma schema](https://github.com/2color/real-world-grading-app/blob/b6caee64a7adbc51735bf389757e1f414b9b7d11/prisma/schema.prisma#L8). ## Defining the build-time secrets in GitHub @@ -240,9 +250,11 @@ For GitHub Actions to run the production database migration and deploy the backe ### Getting the production `DATABASE_URL` To get the `DATABASE_URL`, that has been set by Heroku when the database provisioned, use the following Heroku CLI command: + ``` heroku config:get DATABASE_URL ``` + **Checkpoint:** You should see the URL in the output, e.g., `postgres://username:password@ec2-12.eu-west-1.compute.amazonaws.com:5432/dbname` ### Getting the `HEROKU_API_KEY` @@ -276,22 +288,28 @@ The backend needs three secrets that will be passed to the application as enviro > **Note:** You can generate `JWT_SECRET` by running the following command in the terminal: `node -e "console.log(require('crypto').randomBytes(256).toString('base64'));"` To set them with the Heroku CLI, use the following command: + ``` heroku config:set SENDGRID_API_KEY="REPLACE_WITH_API_KEY" JWT_SECRET="REPLACE_WITH_SECRET" ``` + **Checkpoint:** To verify the environment variables were set, you should see the following: + ``` Setting SENDGRID_API_KEY, JWT_SECRET and restarting ⬢ YOUR_APP_NAME... done, v7 ``` + ## Triggering a workflow to run the tests and deploy With the workflow configured, the app created on Heroku, and all the secrets set, you can now trigger the workflow to run the tests and deploy. To trigger a build, create an empty commit and push it: + ``` git commit --allow-empty -m "Trigger build" git push ``` + Once you have pushed a commit, go to the Actions tab of your GitHub repository and you should see the following: ![](/backend-prisma-typescript-orm-with-postgresql-deployment-bbba1ps7kip5/imgs/modern-backend-4-workflow-run.png) @@ -309,15 +327,19 @@ To view the logs for the `test` job, click on `test` which should allow you to v ## Verifying the deployment to Heroku To verify that `deploy` job successfully deployed to Heroku, click on `deploy` on the left-hand side and unfold the `Deploy to Heroku` step. You should see in the end of the logs the following line: + ``` remote: https://***.herokuapp.com/ deployed to Heroku ``` + ![](/backend-prisma-typescript-orm-with-postgresql-deployment-bbba1ps7kip5/imgs/modern-backend-4-workflow-run-deploy-job.png) To access the API from the browser, use the following Heroku CLI command, from the cloned repository folder: + ``` heroku open ``` + This will open up the browser pointing to `https://YOUR_APP_NAME.herokuapp.com/`. **Checkpoint**: You should see `{"up":true}` in the browser which is served by the [status endpoint](https://github.com/2color/real-world-grading-app/blob/231ab2f32ca7aff6957b58b7395d66de3c51fae0/src/plugins/status.ts). @@ -325,29 +347,39 @@ This will open up the browser pointing to `https://YOUR_APP_NAME.herokuapp.com/` ## Viewing the backend logs To view the backend's logs, use the following Heroku CLI command from the cloned repository folder: + ``` heroku logs --tail -a YOUR_APP_NAME ``` + ## Testing the login flow To test login flow, you will need to make two calls to the REST API. Begin by getting the URL of the API: + ``` heroku apps:info ``` + Make a POST call to the login endpoint with curl: + ``` curl --header "Content-Type: application/json" --request POST --data '{"email":"your-email@prisma.io"}' https://YOUR_APP_NAME.herokuapp.com/login ``` + Check the email for the 8 digit token and then make the second + ``` curl -v --header "Content-Type: application/json" --request POST --data '{"email":"your-email@prisma.io", "emailToken": "99223388"}' https://YOUR_APP_NAME.herokuapp.com/authenticate ``` + **Checkpoint:** The response should have the 200 successful status code and contain the `Authorization` header with the JWT token: + ``` < Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbklkIjo4fQ.ea2lBPMJ6mrPkwEHCgeIFqqQfkQ2uMQ4hL-GCuwtBAE ``` + ## Summary Your backend is now deployed and running. Well done! @@ -365,4 +397,3 @@ You can find the full source code for the backend on [GitHub](https://github.com While Prisma aims to make working with relational databases easy, it's useful to understand the underlying database and [Heroku specific details](https://devcenter.heroku.com/articles/deploying-nodejs). If you have questions, feel free to reach out on [Twitter](https://twitter.com/daniel2color). - diff --git a/apps/blog/content/blog/backend-prisma-typescript-orm-with-postgresql-rest-api-validation-dcba1ps7kip3/index.mdx b/apps/blog/content/blog/backend-prisma-typescript-orm-with-postgresql-rest-api-validation-dcba1ps7kip3/index.mdx index 62e18f8891..d980e27c8d 100644 --- a/apps/blog/content/blog/backend-prisma-typescript-orm-with-postgresql-rest-api-validation-dcba1ps7kip3/index.mdx +++ b/apps/blog/content/blog/backend-prisma-typescript-orm-with-postgresql-rest-api-validation-dcba1ps7kip3/index.mdx @@ -27,19 +27,18 @@ The recording of the live stream is available above and covers the same ground a The series will focus on the role of the database in every aspect of backend development covering: -| Topic | Part | -| ------------------------------ | ------------------------------------------------------------------- | +| Topic | Part | +| ------------------------------ | ------------------------------------------------------------------------------------------------------------- | | Data modeling | [Part 1](https://www.prisma.io/blog/backend-prisma-typescript-orm-with-postgresql-data-modeling-tsjs1ps7kip1) | | CRUD | [Part 1](https://www.prisma.io/blog/backend-prisma-typescript-orm-with-postgresql-data-modeling-tsjs1ps7kip1) | | Aggregations | [Part 1](https://www.prisma.io/blog/backend-prisma-typescript-orm-with-postgresql-data-modeling-tsjs1ps7kip1) | -| REST API layer | Part 2 (current) | -| Validation | Part 2 (current) | -| Testing | Part 2 (current) | -| Authentication | Coming up | -| Authorization | Coming up | -| Integration with external APIs | Coming up | -| Deployment | Coming up | - +| REST API layer | Part 2 (current) | +| Validation | Part 2 (current) | +| Testing | Part 2 (current) | +| Authentication | Coming up | +| Authorization | Coming up | +| Integration with external APIs | Coming up | +| Deployment | Coming up | ### What you will learn today @@ -81,11 +80,13 @@ If you're using Visual Studio Code, the [Prisma extension](https://marketplace.v The source code for the series can be found on [GitHub](https://github.com/2color/real-world-grading-app). To get started, clone the repository and install the dependencies: + ``` git clone -b part-2 git@github.com:2color/real-world-grading-app.git cd real-world-grading-app npm install ``` + > **Note:** By checking out the `part-2` branch you'll be able to follow the article from the same starting point. ## Start PostgreSQL @@ -95,6 +96,7 @@ To start PostgreSQL, run the following command from the `real-world-grading-app` ```sh docker-compose up -d ``` + > **Note:** Docker will use the [`docker-compose.yml`](https://github.com/2color/real-world-grading-app/blob/21de326008776144ced60427a055c9fc54a32840/docker-compose.yml) file to start the PostgreSQL container. ## Building a REST API @@ -166,6 +168,7 @@ Install the following packages: npm install --save @hapi/boom @hapi/hapi @hapi/joi npm install --save-dev @types/hapi__hapi @types/hapi__joi ``` + ### Creating the server The first thing you need to do is create a Hapi server which will bind to an interface and port. @@ -173,31 +176,32 @@ The first thing you need to do is create a Hapi server which will bind to an int Add the following Hapi server to `src/server.ts`: ```ts -import Hapi from '@hapi/hapi' +import Hapi from "@hapi/hapi"; const server: Hapi.Server = Hapi.server({ port: process.env.PORT || 3000, - host: process.env.HOST || 'localhost', -}) + host: process.env.HOST || "localhost", +}); export async function start(): Promise { - await server.start() - return server + await server.start(); + return server; } -process.on('unhandledRejection', err => { - console.log(err) - process.exit(1) -}) +process.on("unhandledRejection", (err) => { + console.log(err); + process.exit(1); +}); start() - .then(server => { - console.log(`Server running on ${server.info.uri}`) - }) - .catch(err => { - console.log(err) + .then((server) => { + console.log(`Server running on ${server.info.uri}`); }) + .catch((err) => { + console.log(err); + }); ``` + First, you import Hapi. Then you initialize a new `Hapi.server()` (of type `Hapi.Server` defined in `@types/hapi__hapi` package) with connection details containing a port number to listen on and the host information. After that you start the server and log that it's running. To run the server locally during development, run the npm `dev`script which will use `ts-node-dev` to automatically transpile the TypeScript code and restart the server when you make changes: `npm run dev`: @@ -210,6 +214,7 @@ npm run dev Using ts-node version 8.10.2, typescript version 3.9.6 Server running on http://localhost:3000 ``` + **Checkpoint:** If you open http://localhost:3000 in your browser, you should see the following: `{"statusCode":404,"error":"Not Found","message":"Not Found"}` Congratulations, you have successfully created a server. However, the server has no routes defined. In the next step, you will define the first route. @@ -223,17 +228,18 @@ To do so, update the `start` function in `server.ts` by adding the following to ```ts export async function start(): Promise { server.route({ - method: 'GET', - path: '/', + method: "GET", + path: "/", handler: (_, h: Hapi.ResponseToolkit) => { - return h.response({ up: true }).code(200) + return h.response({ up: true }).code(200); }, - }) - await server.start() - console.log(`Server running on ${server.info.uri}`) - return server + }); + await server.start(); + console.log(`Server running on ${server.info.uri}`); + return server; } ``` + Here you defined the HTTP method, the path, and a handler which returns the object `{ up: true }` and lastly set the HTTP status code to `200`. **Checkpoint:** If you open http://localhost:3000 in your browser, you should see the following: `{"up":true}` @@ -256,31 +262,34 @@ Begin by creating a new folder in `src/` named `plugins`: ```sh mkdir src/plugins ``` + Create a new file named `status.ts` in the `src/plugins/` folder: ```sh touch src/plugins/status.ts ``` + And add the following to the file: ```ts -import Hapi from '@hapi/hapi' +import Hapi from "@hapi/hapi"; const plugin: Hapi.Plugin = { - name: 'app/status', - register: async function(server: Hapi.Server) { + name: "app/status", + register: async function (server: Hapi.Server) { server.route({ - method: 'GET', - path: '/', + method: "GET", + path: "/", handler: (_, h: Hapi.ResponseToolkit) => { - return h.response({ up: true }).code(200) + return h.response({ up: true }).code(200); }, - }) + }); }, -} +}; -export default plugin +export default plugin; ``` + A Hapi plugin is an object with a `name` property and a `register` function which is where you would typically encapsulate the logic of the plugin. The `name` property the plugin name string and is used as a unique key. Each plugin can manipulate the server through the standard [server interface](https://hapi.dev/api?v=19.2.0#server). In the `app/status` plugin above, `server` is used to define the _status_ route in the `register` function. @@ -290,18 +299,20 @@ Each plugin can manipulate the server through the standard [server interface](ht To register the plugin, go back to `server.ts` and import the status plugin as follows: ```ts -import status from './plugins/status' +import status from "./plugins/status"; ``` + In the `start` function, replace the `route()` call from the previous step with the following [`server.register()`](https://hapi.dev/api?v=19.2.0#-await-serverregisterplugins-options) call: ```ts export async function start(): Promise { - await server.register([status]) - await server.start() - console.log(`Server running on ${server.info.uri}`) - return server + await server.register([status]); + await server.start(); + console.log(`Server running on ${server.info.uri}`); + return server; } ``` + **Checkpoint:** If you open http://localhost:3000 in your browser, you should see the following: `{"up":true}` Congratulations, you have successfully created a Hapi plugin which encapsulates the logic for the status endpoint. @@ -319,27 +330,28 @@ To use the `server.inject` method, you need access in your tests to the `server` ```ts const server: Hapi.Server = Hapi.server({ port: process.env.PORT || 3000, - host: process.env.HOST || 'localhost', -}) + host: process.env.HOST || "localhost", +}); export async function createServer(): Promise { - await server.register([statusPlugin]) - await server.initialize() + await server.register([statusPlugin]); + await server.initialize(); - return server + return server; } export async function startServer(server: Hapi.Server): Promise { - await server.start() - console.log(`Server running on ${server.info.uri}`) - return server + await server.start(); + console.log(`Server running on ${server.info.uri}`); + return server; } -process.on('unhandledRejection', err => { - console.log(err) - process.exit(1) -}) +process.on("unhandledRejection", (err) => { + console.log(err); + process.exit(1); +}); ``` + You just split replaced the `start` function with two functions: - `createServer()`: Registers the plugins and initializes the server @@ -354,14 +366,15 @@ Next, you will create a new entry point for the application which will call both Create a new `src/index.ts` file and add the following to it: ```ts -import { createServer, startServer } from './server' +import { createServer, startServer } from "./server"; createServer() .then(startServer) - .catch(err => { - console.log(err) - }) + .catch((err) => { + console.log(err); + }); ``` + Lastly, update the `dev` script in `package.json` to start `src/index.ts` instead of `src/server.ts`: ```json @@ -369,36 +382,38 @@ diff - "dev": "ts-node-dev --respawn ./src/server.ts", + "dev": "ts-node-dev --respawn ./src/index.ts", ``` + #### Creating the test To create the test, create a folder named `tests` in the root of the project and create a file named `status.test.ts` and add the following to the file: ```ts -import { createServer } from '../src/server' -import Hapi from '@hapi/hapi' +import { createServer } from "../src/server"; +import Hapi from "@hapi/hapi"; -describe('Status plugin', () => { - let server: Hapi.Server +describe("Status plugin", () => { + let server: Hapi.Server; beforeAll(async () => { - server = await createServer() - }) + server = await createServer(); + }); afterAll(async () => { - await server.stop() - }) + await server.stop(); + }); - test('status endpoint returns 200', async () => { + test("status endpoint returns 200", async () => { const res = await server.inject({ - method: 'GET', - url: '/', - }) - expect(res.statusCode).toEqual(200) - const response = JSON.parse(res.payload) - expect(response.up).toEqual(true) - }) -}) + method: "GET", + url: "/", + }); + expect(res.statusCode).toEqual(200); + const response = JSON.parse(res.payload); + expect(response.up).toEqual(true); + }); +}); ``` + In the test above, the `beforeAll` and `afterAll` are used as [setup and teardown](https://jestjs.io/docs/en/setup-teardown) functions to create and stop the server. Then, the `server.inject` is called to simulate a `GET` HTTP request to the root endpoint `/`. The test then asserts the HTTP status code and the payload to ensure it matches the handler. @@ -416,6 +431,7 @@ Snapshots: 0 total Time: 0.886 s, estimated 1 s Ran all test suites. ``` + Congratulations, you have created a plugin with a route and tested the route. In the next step, you will define a Prisma plugin so that you can access the Prisma Client instance throughout the application. @@ -429,37 +445,39 @@ The goal of the Prisma plugin is to instantiate the Prisma Client, make it avail Add the following to the `src/plugins/prisma.ts` file: ```ts -import { PrismaClient } from '@prisma/client' -import Hapi from '@hapi/hapi' +import { PrismaClient } from "@prisma/client"; +import Hapi from "@hapi/hapi"; // plugin to instantiate Prisma Client const prismaPlugin: Hapi.Plugin = { - name: 'prisma', - register: async function(server: Hapi.Server) { - const prisma = new PrismaClient() + name: "prisma", + register: async function (server: Hapi.Server) { + const prisma = new PrismaClient(); - server.app.prisma = prisma + server.app.prisma = prisma; // Close DB connection after the server's connection listeners are stopped // Related issue: https://github.com/hapijs/hapi/issues/2839 server.ext({ - type: 'onPostStop', + type: "onPostStop", method: async (server: Hapi.Server) => { - server.app.prisma.disconnect() + server.app.prisma.disconnect(); }, - }) + }); }, -} +}; -export default prismaPlugin +export default prismaPlugin; ``` + Here we define a plugin, instantiate Prisma Client, assign it to `server.app`, and add an extension function (can be thought of as a hook) that will run on the `onPostStop` event which gets called after the server's connection listeners are stopped. To register the Prisma plugin, import the plugin in `server.ts` and add it to the array passed to the `server.register` call as follows: ```ts -await server.register([status, prisma]) +await server.register([status, prisma]); ``` + If you're using VSCode, you will see a red squiggly line below `server.app.prisma = prisma` in the `src/plugins/prisma.ts` file. This is the first type error you encounter. If you don't see the line, you can run the `compile` script to run the TypeScript compiler: ```sh @@ -469,15 +487,17 @@ src/plugins/prisma.ts:21:16 - error TS2339: Property 'prisma' does not exist on 21 server.app.prisma = prisma ``` + This reason for this error is that you've modified the `server.app` without updating its type. To resolve the error, add the following on top of the `prismaPlugin` definition: ```ts -declare module '@hapi/hapi' { +declare module "@hapi/hapi" { interface ServerApplicationState { - prisma: PrismaClient + prisma: PrismaClient; } } ``` + This will augment the module and assign the `PrismaClient` type to the `server.app.prisma` property. > **Note:** For more information about why module augmentation is necessary, check out [this comment](https://github.com/DefinitelyTyped/DefinitelyTyped/issues/33809#issuecomment-472103564) in the DefinitelyTyped repository. @@ -499,18 +519,19 @@ Begin by creating a new file `src/plugins/users.ts` file for the users plugin. Add the following to the file: ```ts -import Hapi from '@hapi/hapi' +import Hapi from "@hapi/hapi"; // plugin to instantiate Prisma Client const usersPlugin = { - name: 'app/users', - dependencies: ['prisma'], - register: async function(server: Hapi.Server) { + name: "app/users", + dependencies: ["prisma"], + register: async function (server: Hapi.Server) { // here you can use server.app.prisma }, -} -export default usersPlugin +}; +export default usersPlugin; ``` + Here you passed an array to the `dependencies` property to make sure Hapi loads the Prisma plugin first. You can now define the user-specific routes in the `register` function knowing that Prisma Client will be accessible. @@ -518,10 +539,9 @@ You can now define the user-specific routes in the `register` function knowing t Lastly, you will need to import the plugin and register it in `src/server.ts` as follows: ```ts -diff --await server.register([status, prisma]) -+await server.register([status, prisma, users]) +diff - (await server.register([status, prisma])) + (await server.register([status, prisma, users])); ``` + In the next step, you will define a create user endpoint. ### Defining the create user route @@ -535,18 +555,19 @@ Begin by adding the following `server.route` call in `src/plugins/users.ts` insi ```ts server.route([ { - method: 'POST', - path: '/users', + method: "POST", + path: "/users", handler: createUserHandler, }, -]) +]); ``` + Then define the `createUserHandler` function as follows: ```ts async function createUserHandler(request: Hapi.Request, h: Hapi.ResponseToolkit) { - const { prisma } = request.server.app - const payload = request.payload + const { prisma } = request.server.app; + const payload = request.payload; try { const createdUser = await prisma.user.create({ @@ -559,13 +580,14 @@ async function createUserHandler(request: Hapi.Request, h: Hapi.ResponseToolkit) select: { id: true, }, - }) - return h.response(createdUser).code(201) + }); + return h.response(createdUser).code(201); } catch (err) { - console.log(err) + console.log(err); } } ``` + Here you access `prisma` from the `server.app` object (assigned in the Prisma plugin), and use the request payload in the `prisma.user.create` call to save the user in the database. You should see a red squiggly line again below the lines accessing `payload`'s properties', indicating a type error. If you don't see the error, run the TypeScript compiler again: @@ -578,6 +600,7 @@ src/plugins/users.ts:27:28 - error TS2339: Property 'firstName' does not exist o 27 firstName: payload.firstName, ~~~~~~~~~ ``` + This is because `payload`'s value is determined at runtime, so the TypeScript compiler has no way of knowing its time. This can be fixed with a **type assertion**. Type assertion is a mechanism in TypeScript that allows you to override a variable's inferred type. TypeScript's type assertion is purely you telling the compiler that you know about the types better than it does as here. @@ -585,59 +608,61 @@ To do so, define an interface for the expected payload: ```ts interface UserInput { - firstName: string - lastName: string - email: string + firstName: string; + lastName: string; + email: string; social: { - facebook?: string - twitter?: string - github?: string - website?: string - } + facebook?: string; + twitter?: string; + github?: string; + website?: string; + }; } ``` + > **Note:** Types and Interfaces have many similarities in TypeScript. Then add the type assertion: ```ts -const payload = request.payload as UserInput +const payload = request.payload as UserInput; ``` + The plugin should look as follows: ```ts // plugin to instantiate Prisma Client const usersPlugin = { - name: 'app/users', - dependencies: ['prisma'], - register: async function(server: Hapi.Server) { + name: "app/users", + dependencies: ["prisma"], + register: async function (server: Hapi.Server) { server.route([ { - method: 'POST', - path: '/users', + method: "POST", + path: "/users", handler: registerHandler, }, - ]) + ]); }, -} +}; -export default usersPlugin +export default usersPlugin; interface UserInput { - firstName: string - lastName: string - email: string + firstName: string; + lastName: string; + email: string; social: { - facebook?: string - twitter?: string - github?: string - website?: string - } + facebook?: string; + twitter?: string; + github?: string; + website?: string; + }; } async function registerHandler(request: Hapi.Request, h: Hapi.ResponseToolkit) { - const { prisma } = request.server.app - const payload = request.payload as UserInput + const { prisma } = request.server.app; + const payload = request.payload as UserInput; try { const createdUser = await prisma.user.create({ @@ -650,13 +675,14 @@ async function registerHandler(request: Hapi.Request, h: Hapi.ResponseToolkit) { select: { id: true, }, - }) - return h.response(createdUser).code(201) + }); + return h.response(createdUser).code(201); } catch (err) { - console.log(err) + console.log(err); } } ``` + ### Adding validation to the create user route In this step, you will also add payload validation using Joi to ensure the route only handles requests with the correct data. @@ -666,42 +692,43 @@ Validation can be thought of as a runtime type check. When using TypeScript, the To do so, import Joi as follows: ```ts -import Joi from '@hapi/joi' +import Joi from "@hapi/joi"; ``` + Joi allows you to define validation rules by creating a Joi validation object which can be assigned to the route handler so that Hapi will know to validate the payload. In the create user endpoint, you want to validate that the user input fits the type you've defined above: ```ts interface UserInput { - firstName: string - lastName: string - email: string + firstName: string; + lastName: string; + email: string; social: { - facebook?: string - twitter?: string - github?: string - website?: string - } + facebook?: string; + twitter?: string; + github?: string; + website?: string; + }; } ``` + The Joi corresponding validation object would look as follows: ```ts const userInputValidator = Joi.object({ firstName: Joi.string().required(), lastName: Joi.string().required(), - email: Joi.string() - .email() - .required(), + email: Joi.string().email().required(), social: Joi.object({ facebook: Joi.string().optional(), twitter: Joi.string().optional(), github: Joi.string().optional(), website: Joi.string().optional(), }).optional(), -}) +}); ``` + Next, you have to configure the route handler to use the validator object `userInputValidator`. Add the following to your route definition object: ```json @@ -717,6 +744,7 @@ diff + }, } ``` + ### Create a test for the create user route In this step, you will create a test to verify the create user logic. The test will make a request to the `POST /users` endpoint with `server.inject` and check that the response includes the `id` field thereby verifying that the user has been created in the database. @@ -724,44 +752,44 @@ In this step, you will create a test to verify the create user logic. The test w Start by creating a `tests/users.tests.ts` file and add the following contents: ```ts -import { createServer } from '../src/server' -import Hapi from '@hapi/hapi' +import { createServer } from "../src/server"; +import Hapi from "@hapi/hapi"; -describe('POST /users - create user', () => { - let server: Hapi.Server +describe("POST /users - create user", () => { + let server: Hapi.Server; beforeAll(async () => { - server = await createServer() - }) + server = await createServer(); + }); afterAll(async () => { - await server.stop() - }) + await server.stop(); + }); - let userId + let userId; - test('create user', async () => { + test("create user", async () => { const response = await server.inject({ - method: 'POST', - url: '/users', + method: "POST", + url: "/users", payload: { - firstName: 'test-first-name', - lastName: 'test-last-name', + firstName: "test-first-name", + lastName: "test-last-name", email: `test-${Date.now()}@prisma.io`, social: { - twitter: 'thisisalice', - website: 'https://www.thisisalice.com' - } - } - }) - - expect(response.statusCode).toEqual(201) - userId = JSON.parse(response.payload)?.id - expect(typeof userId === 'number').toBeTruthy() - }) + twitter: "thisisalice", + website: "https://www.thisisalice.com", + }, + }, + }); -}) + expect(response.statusCode).toEqual(201); + userId = JSON.parse(response.payload)?.id; + expect(typeof userId === "number").toBeTruthy(); + }); +}); ``` + The test injects a request with a payload and asserts the `statusCode` and that the `id` in the response is a number. > **Note:** The test avoids unique constraint errors by ensuring that the `email` is unique on every test run. @@ -769,24 +797,25 @@ The test injects a request with a payload and asserts the `statusCode` and that Now that you've written a test for the happpy path (creating a user sucessfully), you will write another test to verify the validation logic. You will do so by crafting another request with invalid payload, e.g. ommitting the required field `firstName` as follows: ```ts -test('create user validation', async () => { +test("create user validation", async () => { const response = await server.inject({ - method: 'POST', - url: '/users', + method: "POST", + url: "/users", payload: { - lastName: 'test-last-name', + lastName: "test-last-name", email: `test-${Date.now()}@prisma.io`, social: { - twitter: 'thisisalice', - website: 'https://www.thisisalice.com', + twitter: "thisisalice", + website: "https://www.thisisalice.com", }, }, - }) + }); - console.log(response.payload) - expect(response.statusCode).toEqual(400) -}) + console.log(response.payload); + expect(response.statusCode).toEqual(400); +}); ``` + **Checkpoint:** Run the tests with the `npm test` command and verify that all tests pass. ### Defining and testing the get user route @@ -804,29 +833,31 @@ First, you will test the route returning 404 when a user is not found. Open the `users.test.ts` file and add the following `test`: ```ts -test('get user returns 404 for non existant user', async () => { +test("get user returns 404 for non existant user", async () => { const response = await server.inject({ - method: 'GET', - url: '/users/9999', - }) + method: "GET", + url: "/users/9999", + }); - expect(response.statusCode).toEqual(404) -}) + expect(response.statusCode).toEqual(404); +}); ``` + The second test will test the happy path – a successfully retrieved user. You will use the `userId` variable set in the create user test created in the previous step. This will ensure that you fetch an existing user. Add the following test: ```ts -test('get user returns user', async () => { +test("get user returns user", async () => { const response = await server.inject({ - method: 'GET', + method: "GET", url: `/users/${userId}`, - }) - expect(response.statusCode).toEqual(200) - const user = JSON.parse(response.payload) + }); + expect(response.statusCode).toEqual(200); + const user = JSON.parse(response.payload); - expect(user.id).toBe(userId) -}) + expect(user.id).toBe(userId); +}); ``` + Since you haven't defined the route yet, running the tests now will result in failing tests. The next step will be to define the route. #### Defining the route @@ -836,8 +867,8 @@ Go to the `users.ts` (users plugin) and add the following route object to the `s ```ts server.route([ { - method: 'GET', - path: '/users/{userId}', + method: "GET", + path: "/users/{userId}", handler: getUserHandler, options: { validate: { @@ -847,34 +878,36 @@ server.route([ }, }, }, -]) +]); ``` + Similar to how you defined validation rules for the create user endpoint, in the route definition above you validate the `userId` url parameter to ensure a number is passed. Next, define the `getUserHandler` function as follows: ```ts async function getUserHandler(request: Hapi.Request, h: Hapi.ResponseToolkit) { - const { prisma } = request.server.app - const userId = parseInt(request.params.userId, 10) + const { prisma } = request.server.app; + const userId = parseInt(request.params.userId, 10); try { const user = await prisma.user.findUnique({ where: { id: userId, }, - }) + }); if (!user) { - return h.response().code(404) + return h.response().code(404); } else { - return h.response(user).code(200) + return h.response(user).code(200); } } catch (err) { - console.log(err) - return Boom.badImplementation() + console.log(err); + return Boom.badImplementation(); } } ``` + > **Note:** when calling `findUnique`, Prisma will return `null` if no result could be found. In the handler, the `userId` is parsed from the request parameters and used in a Prisma Client query. If the user cannot be found `404` is returned, otherwise, the found user object is returned. @@ -892,25 +925,27 @@ The delete user endpoint will have the `DELETE /users/{userId}` signature. First, you will write a test for the route's parameter valdation. Add the following test to `users.test.ts`: ```ts -test('delete user fails with invalid userId parameter', async () => { +test("delete user fails with invalid userId parameter", async () => { const response = await server.inject({ - method: 'DELETE', + method: "DELETE", url: `/users/aa22`, - }) - expect(response.statusCode).toEqual(400) -}) + }); + expect(response.statusCode).toEqual(400); +}); ``` + Then add another test for the delete user logic in which you will delete the user created in the create user test: ```ts -test('delete user', async () => { +test("delete user", async () => { const response = await server.inject({ - method: 'DELETE', + method: "DELETE", url: `/users/${userId}`, - }) - expect(response.statusCode).toEqual(204) -}) + }); + expect(response.statusCode).toEqual(204); +}); ``` + > **Note:** The 204 status response code indicates that the request has succeeded, but the response has no content. #### Defining the route @@ -920,8 +955,8 @@ Go to the `users.ts` (users plugin) and add the following route object to the `s ```ts server.route([ { - method: 'DELETE', - path: '/users/{userId}', + method: "DELETE", + path: "/users/{userId}", handler: deleteUserHandler, options: { validate: { @@ -931,28 +966,30 @@ server.route([ }, }, }, -]) +]); ``` + After you've defined the route, define the `deleteUserHandler` as follows: ```ts async function deleteUserHandler(request: Hapi.Request, h: Hapi.ResponseToolkit) { - const { prisma } = request.server.app - const userId = parseInt(request.params.userId, 10) + const { prisma } = request.server.app; + const userId = parseInt(request.params.userId, 10); try { await prisma.user.delete({ where: { id: userId, }, - }) - return h.response().code(204) + }); + return h.response().code(204); } catch (err) { - console.log(err) - return h.response().code(500) + console.log(err); + return h.response().code(500); } } ``` + **Checkpoint:** Run the tests with `npm test` and verify that all tests have passed. ### Defining and testing the update user route @@ -966,35 +1003,37 @@ The update user endpoint will have the `PUT /users/{userId}` signature. First, you will write a test for the route's parameter valdation. Add the following test to `users.test.ts`: ```ts -test('update user fails with invalid userId parameter', async () => { +test("update user fails with invalid userId parameter", async () => { const response = await server.inject({ - method: 'PUT', + method: "PUT", url: `/users/aa22`, - }) - expect(response.statusCode).toEqual(400) -}) + }); + expect(response.statusCode).toEqual(400); +}); ``` + Add another test for the update user endpoint in which you update the user's `firstName`and `lastName` fields (for the user created in the create user test): ```ts -test('update user', async () => { - const updatedFirstName = 'test-first-name-UPDATED' - const updatedLastName = 'test-last-name-UPDATED' +test("update user", async () => { + const updatedFirstName = "test-first-name-UPDATED"; + const updatedLastName = "test-last-name-UPDATED"; const response = await server.inject({ - method: 'PUT', + method: "PUT", url: `/users/${userId}`, payload: { firstName: updatedFirstName, lastName: updatedLastName, }, - }) - expect(response.statusCode).toEqual(200) - const user = JSON.parse(response.payload) - expect(user.firstName).toEqual(updatedFirstName) - expect(user.lastName).toEqual(updatedLastName) -}) + }); + expect(response.statusCode).toEqual(200); + const user = JSON.parse(response.payload); + expect(user.firstName).toEqual(updatedFirstName); + expect(user.lastName).toEqual(updatedLastName); +}); ``` + #### Defining the update user validation rules In this step you will define the update user route. In terms of validation, the endpoint's payload should not require any specific fields (unlike the create user endpoint where `email`, `firstName`, and `lastName` are required). This will allow you to use the endpoint to update a single field, e.g. `firstName`. @@ -1005,17 +1044,16 @@ To define the payload validation, you _could_ use the `userInputValidator` Joi o const userInputValidator = Joi.object({ firstName: Joi.string().required(), lastName: Joi.string().required(), - email: Joi.string() - .email() - .required(), + email: Joi.string().email().required(), social: Joi.object({ facebook: Joi.string().optional(), twitter: Joi.string().optional(), github: Joi.string().optional(), website: Joi.string().optional(), }).optional(), -}) +}); ``` + In the update user endpoint, all fields should be optional. Joi provides a way to create different alterations of the same Joi object using the [`tailor` and `alter` methods](https://github.com/sideway/joi/blob/master/API.md#anytailortargets). This is especially useful when defining create and update routes that have similar validation rules while keeping the code [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself). Update the already defined `userInputValidator` as follows: @@ -1023,18 +1061,18 @@ Update the already defined `userInputValidator` as follows: ```ts const userInputValidator = Joi.object({ firstName: Joi.string().alter({ - create: schema => schema.required(), - update: schema => schema.optional(), + create: (schema) => schema.required(), + update: (schema) => schema.optional(), }), lastName: Joi.string().alter({ - create: schema => schema.required(), - update: schema => schema.optional(), + create: (schema) => schema.required(), + update: (schema) => schema.optional(), }), email: Joi.string() .email() .alter({ - create: schema => schema.required(), - update: schema => schema.optional(), + create: (schema) => schema.required(), + update: (schema) => schema.optional(), }), social: Joi.object({ facebook: Joi.string().optional(), @@ -1042,11 +1080,12 @@ const userInputValidator = Joi.object({ github: Joi.string().optional(), website: Joi.string().optional(), }).optional(), -}) +}); -const createUserValidator = userInputValidator.tailor('create') -const updateUserValidator = userInputValidator.tailor('update') +const createUserValidator = userInputValidator.tailor("create"); +const updateUserValidator = userInputValidator.tailor("update"); ``` + #### Updating the create user route's payload validation Now you can update the create user route definition to use `createUserValidator` in `src/plugins/users.ts` (users plugin): @@ -1065,6 +1104,7 @@ diff } } ``` + #### Defining the update user route With the validation object for update defined, you can now define the update user route. @@ -1086,13 +1126,14 @@ server.route([ }, ]) ``` + After you've defined the route, define the `updateUserHandler` function as follows: ```ts async function updateUserHandler(request: Hapi.Request, h: Hapi.ResponseToolkit) { - const { prisma } = request.server.app - const userId = parseInt(request.params.userId, 10) - const payload = request.payload as Partial + const { prisma } = request.server.app; + const userId = parseInt(request.params.userId, 10); + const payload = request.payload as Partial; try { const updatedUser = await prisma.user.update({ @@ -1100,14 +1141,15 @@ async function updateUserHandler(request: Hapi.Request, h: Hapi.ResponseToolkit) id: userId, }, data: payload, - }) - return h.response(updatedUser).code(200) + }); + return h.response(updatedUser).code(200); } catch (err) { - console.log(err) - return h.response().code(500) + console.log(err); + return h.response().code(500); } } ``` + **Checkpoint:** Run the tests with `npm test` and verify that all tests have passed. ## Summary and next steps @@ -1135,4 +1177,3 @@ In the next parts of the series, you'll learn more about: - Integration with external APIs: Using a transactional email API to send emails. - Authorization: Provide different levels of access to different resources. - Deployment - diff --git a/apps/blog/content/blog/benefits-and-challenges-of-caching-database-query-results-x2s9ei21e8kq/index.mdx b/apps/blog/content/blog/benefits-and-challenges-of-caching-database-query-results-x2s9ei21e8kq/index.mdx index 8cc333f93b..ff9d15f511 100644 --- a/apps/blog/content/blog/benefits-and-challenges-of-caching-database-query-results-x2s9ei21e8kq/index.mdx +++ b/apps/blog/content/blog/benefits-and-challenges-of-caching-database-query-results-x2s9ei21e8kq/index.mdx @@ -14,8 +14,8 @@ tags: --- Caching database query results can have amazing benefits for performance, cost efficiency and user - experience! In this article, we'll talk about these benefits as well as the challenges and drawbacks of database - caching. +experience! In this article, we'll talk about these benefits as well as the challenges and drawbacks of database +caching. ## Table of contents @@ -40,7 +40,7 @@ When creating a web application, retrieving data from a database is essential. H ### Caching significantly improves performance -Using a cache to store database query results can significantly boost the performance of your application. A database cache is much faster and usually hosted closer to the application server, which reduces the load on the main database, accelerates data retrieval, and minimizes network and query latency. +Using a cache to store database query results can significantly boost the performance of your application. A database cache is much faster and usually hosted closer to the application server, which reduces the load on the main database, accelerates data retrieval, and minimizes network and query latency. ![Performance without cache](/benefits-and-challenges-of-caching-database-query-results-x2s9ei21e8kq/imgs/2-a-without-cache.png) ![Performance with cache](/benefits-and-challenges-of-caching-database-query-results-x2s9ei21e8kq/imgs/2-a-with-cache.png) @@ -137,7 +137,6 @@ Debugging and troubleshooting can be challenging when issues arise with the cach ## Wrapping up -In conclusion, when implemented correctly, database caching can significantly enhance your application's performance. Using a cache to store query results, you can effectively address high query latencies and greatly improve your application's responsiveness. So, don't hesitate to leverage the power of database caching to unlock a smoother and more efficient user experience. +In conclusion, when implemented correctly, database caching can significantly enhance your application's performance. Using a cache to store query results, you can effectively address high query latencies and greatly improve your application's responsiveness. So, don't hesitate to leverage the power of database caching to unlock a smoother and more efficient user experience. > At Prisma, we aim to simplify the process of caching for developers. We understand that setting up a complex infrastructure can be tricky and time-consuming, so [we built Accelerate as a solution](https://www.prisma.io/data-platform/accelerate) that makes it easy to cache your database query results in a simple and predictable way. Follow us on [X/Twitter](https://pris.ly/x) or join us on [Discord](https://pris.ly/discord) to learn more about the tools we're building. - diff --git a/apps/blog/content/blog/bfg/index.mdx b/apps/blog/content/blog/bfg/index.mdx index 3ebc85d882..39caf8bf53 100644 --- a/apps/blog/content/blog/bfg/index.mdx +++ b/apps/blog/content/blog/bfg/index.mdx @@ -16,35 +16,39 @@ tags: Dive into Prisma’s ‘Build, Fortify, Grow’ framework. Learn how Prisma products interoperate at each stage of the framework, and aid in enhancing data-driven application development. -We’ll start by thanking our community for the positive feedback on our 'Build, Fortify, Grow' framework, which is part of the [Data DX](https://datadx.io) initiative. We launched this framework a few months ago via our [homepage](https://prisma.io) not only to help our community better understand the thought process and product planning at Prisma but also to demonstrate how Prisma products provide the right tools for developers at each stage of the application development lifecycle. +We’ll start by thanking our community for the positive feedback on our 'Build, Fortify, Grow' framework, which is part of the [Data DX](https://datadx.io) initiative. We launched this framework a few months ago via our [homepage](https://prisma.io) not only to help our community better understand the thought process and product planning at Prisma but also to demonstrate how Prisma products provide the right tools for developers at each stage of the application development lifecycle. It’s encouraging to see how the framework has resonated with you. Many have expressed interest in learning more about how each part of the framework can assist in your development efforts. In this blog post, we'll delve deeper into how these principles can enhance your data-driven projects. ### Build: Streamline development. Iterate fast! -The 'Build' phase is designed to simplify the initiation of your project. It allows you to focus on making database operations straightforward, especially for those who prefer not to delve deep into SQL. During this phase, iteration speed is important, and we recognize that. -By leveraging [Prisma’s ORM](https://prisma.io/orm), teams can efficiently manage CRUD (Create, Read, Update, Delete) operations without the need for extensive SQL knowledge. This allows you and your team to iterate faster and be more effective. You focus on application logic rather than database syntax. Prisma ORM automates much of the database schema management, facilitating rapid development cycles and reducing the risk of manual errors in database handling. This approach also highlights how larger teams can operate at break-neck speeds and reduce knowledge dependence. Read more on our thoughts on this topic in our [Enterprise section](https://prisma.io/enterprise). +The 'Build' phase is designed to simplify the initiation of your project. It allows you to focus on making database operations straightforward, especially for those who prefer not to delve deep into SQL. During this phase, iteration speed is important, and we recognize that. + +By leveraging [Prisma’s ORM](https://prisma.io/orm), teams can efficiently manage CRUD (Create, Read, Update, Delete) operations without the need for extensive SQL knowledge. This allows you and your team to iterate faster and be more effective. You focus on application logic rather than database syntax. Prisma ORM automates much of the database schema management, facilitating rapid development cycles and reducing the risk of manual errors in database handling. This approach also highlights how larger teams can operate at break-neck speeds and reduce knowledge dependence. Read more on our thoughts on this topic in our [Enterprise section](https://prisma.io/enterprise). > If you are looking for something that gives you more control over the underlying SQL, stay tuned, we’ve got something special brewing that we’ll share soon. 👀 **Applicability:** Prisma’s approach to the ‘Build’ phase is particularly beneficial for teams looking to expedite their development processes and for projects where quick prototyping, frequent iterations, and knowledge sharing are critical. ### Fortify: Consistent performance -The 'Fortify' phase is all about enhancing the performance and scalability of your application through intelligent data management and query optimization. It involves refining your database and queries to ensure they are running optimally. Prisma’s ORM, for instance, automatically fine-tunes your queries to enhance database performance, ensuring your application can handle increased loads effortlessly. -What happens if your application experiences spikes? Will those *Black Friday Deals* bring down your infra? This is where [Prisma Accelerate](https://prisma.io/data-platform/accelerate) offers powerful features that integrate a global database cache and scalable connection pool, making your database interactions up to 1000 times faster. This dramatically reduces database query latency, often down to as little as 5 milliseconds, which significantly decreases the load on your database and improves response times. All this makes your application resilient to usage spikes. To us, once you’ve built an application, fortification seems like the next logical step. +The 'Fortify' phase is all about enhancing the performance and scalability of your application through intelligent data management and query optimization. It involves refining your database and queries to ensure they are running optimally. Prisma’s ORM, for instance, automatically fine-tunes your queries to enhance database performance, ensuring your application can handle increased loads effortlessly. + +What happens if your application experiences spikes? Will those _Black Friday Deals_ bring down your infra? This is where [Prisma Accelerate](https://prisma.io/data-platform/accelerate) offers powerful features that integrate a global database cache and scalable connection pool, making your database interactions up to 1000 times faster. This dramatically reduces database query latency, often down to as little as 5 milliseconds, which significantly decreases the load on your database and improves response times. All this makes your application resilient to usage spikes. To us, once you’ve built an application, fortification seems like the next logical step. **Applicability:** This stage is crucial for systems that require high performance under varying loads, particularly those deployed in serverless architectures where managing connection pools and reducing latency are paramount for maintaining smooth and efficient operations. ### Grow: Adapt as your app evolves -The 'Grow' phase is centered on equipping your application to seamlessly adapt as users demand more features and functions. With the integration of Prisma Accelerate in your application, your data layer becomes more dynamic and responsive to changes, regardless of the scale. After Building and Fortifying your application, the next natural evolution is to allow for it to Grow because, let's face it, user needs are never static! We’ve designed and developed our products to help you focus on application logic so that you can *outsource* the data-heavy elements to us. + +The 'Grow' phase is centered on equipping your application to seamlessly adapt as users demand more features and functions. With the integration of Prisma Accelerate in your application, your data layer becomes more dynamic and responsive to changes, regardless of the scale. After Building and Fortifying your application, the next natural evolution is to allow for it to Grow because, let's face it, user needs are never static! We’ve designed and developed our products to help you focus on application logic so that you can _outsource_ the data-heavy elements to us. Within the Grow phase, Prisma Accelerate plays a crucial role in scaling by offering a global database cache that can significantly improve the performance of your queries, especially in serverless environments. It reduces the latency of database operations and allows for a scalable connection pool, ensuring that your application can handle increased traffic without overloading the database servers. **Applicability:** This stage is targeted at applications that are in the scaling phase or adding new functionalities, ensuring that growth is manageable and sustainable without compromising the system’s integrity. ### Cementing 'Build, Fortify, Grow' in your mind -Just like the "[Big F****** Gun"](https://en.wikipedia.org/wiki/BFG_(weapon)) from the massively popular games Doom & Quake, the 'Build, Fortify, Grow' framework (or BFG for short) provides a formidable toolkit for software development teams. With Prisma’s suite of products underpinning each phase, your development process isn't just equipped with a peashooter or a sidearm; it’s armed with the ultimate weapon when it comes to developing data-driven applications. + +Just like the "[Big F**\*\*** Gun"]() from the massively popular games Doom & Quake, the 'Build, Fortify, Grow' framework (or BFG for short) provides a formidable toolkit for software development teams. With Prisma’s suite of products underpinning each phase, your development process isn't just equipped with a peashooter or a sidearm; it’s armed with the ultimate weapon when it comes to developing data-driven applications. In the Doom gaming universe, wielding the BFG means you’re clearing rooms of adversaries with unmatched power. In the world of software development, adopting the BFG framework means you’re blasting through development hurdles, performance bottlenecks, and scalability challenges with similar might and flair. diff --git a/apps/blog/content/blog/bringing-prisma-orm-to-react-native-and-expo/index.mdx b/apps/blog/content/blog/bringing-prisma-orm-to-react-native-and-expo/index.mdx index a80aa6ad75..aa18d2cd57 100644 --- a/apps/blog/content/blog/bringing-prisma-orm-to-react-native-and-expo/index.mdx +++ b/apps/blog/content/blog/bringing-prisma-orm-to-react-native-and-expo/index.mdx @@ -12,7 +12,7 @@ tags: - "release" --- -Prisma ORM now provides Early Access support for React Native and Expo, fulfilling a popular community request. The integration introduces *reactive queries*, using React hooks to auto-update the UI when the underlying data changes. +Prisma ORM now provides Early Access support for React Native and Expo, fulfilling a popular community request. The integration introduces _reactive queries_, using React hooks to auto-update the UI when the underlying data changes. Prisma ORM is the preferred way to work with databases in backend JavaScript applications. This is due to its excellent type-safety, easy migration system and tight integration into your favorite IDE. @@ -49,20 +49,23 @@ export default function TransactionList() { ); } ``` + In this component, we declare the data dependency at the top. Instead of using Prisma ORM’s normal `findMany()` query function, we use the new `useFindMany()` query function which integrates directly with the React `useState()` and `useEffect()` mechanisms to re-render the component when the underlying data changes. This line initially returns an empty array and then re-renders the component as soon as the list of transactions is fetched from the local database: ```ts -prisma.transactions.useFindMany({ orderBy: { date: "desc" }}) +prisma.transactions.useFindMany({ orderBy: { date: "desc" } }); ``` + > It is customary for hooks in React to be free standing functions - for example `useFindManyTransactions()`. To conform with the regular Prisma ORM API, we have chosen the alternative format `prisma.transactions.useFindMany()`. During this Early Access period, we are soliciting feedback on this decision. Please share your thoughts on [Discord](pris.ly/discord). In the `LongPress` handler, the database row is deleted, automatically triggering a re-render of the component. It’s important to note that data changes can happen anywhere in your application, and it will trigger a re-render of any active component that relies on that data. ```ts -() => prisma.transactions.delete({ where: { id: transaction.id } }) +() => prisma.transactions.delete({ where: { id: transaction.id } }); ``` + By taking advantage of Reactive Queries, many applications can be refactored to remove brittle and manual state management in favor of a simple automated reactivity model. ## Prisma ORM in your Expo App today diff --git a/apps/blog/content/blog/build-an-app-with-svelte-and-typescript-PZDY3t93qAtd/index.mdx b/apps/blog/content/blog/build-an-app-with-svelte-and-typescript-PZDY3t93qAtd/index.mdx index 67e7983c8f..ef1f5f6391 100644 --- a/apps/blog/content/blog/build-an-app-with-svelte-and-typescript-PZDY3t93qAtd/index.mdx +++ b/apps/blog/content/blog/build-an-app-with-svelte-and-typescript-PZDY3t93qAtd/index.mdx @@ -32,66 +32,48 @@ After installation, we'll enable TypeScript in the app using the setup script th Use `degit` to create a new project. - - ```shell npx degit sveltejs/template github-users-app ``` - Once the template downloads, open up the project in your editor and have a look in `src/App.svelte`. The template gives us a standard `.svelte` file with a `script` block. If we tried to use TypeScript here right away, we'd get an error. - - ```svelte ``` - To use TypeScript in this block, we need to opt-into it by setting `lang="ts"` in the `script` tag. In `App.svelte`, set the `lang` attribute to `ts`. - - ```svelte ``` - Our last step is to run the `setupTypeScript.js` file that Svelte provides so that we can enable TypeScript properly throughout the app. - - ```shell node scripts/setupTypeScript.js ``` - This script creates a `tsconfig.json` file at the root of the project and converts all the `.js` files to `.ts`. It also adds some new dependencies to the `package.json` file which support TypeScript. Reinstall the dependencies to get the new ones we need. - - ```shell npm install ``` - With these steps complete, we should be able to run the app and see everything working. - - ```shell npm run dev ``` - Open the app up at `http://localhost:5000`. ![Svelte app running at http:./imgs/svelte-typescript-1.png) @@ -104,8 +86,6 @@ Let's start building out the app by creating a component to show some users from Create a file in the `/src` directory called `Users.svelte`. Inside, add a `script` block with a function to get some users from the GitHub API. Be sure to opt-into TypeScript with the `lang` attribute. - - ```svelte ``` - In `App.svelte`, import and use the `Users` component so we can see the results from the API call logged to the console. - - ```svelte ``` - The return type of the `getUsers` function is now type-hinted as a `Promise` that resolves with an array of objects that are of type `User`. You may be wondering why we're relying on this spot to type-hint our data. It's because we can't apply a type hint to the other spots we might think to. We can't type-hint the `$: allUsersPromise` declaration, nor can we apply types in our template. Type-hinting the return of the function gives us some type safety in a way that is workable with Svelte. @@ -240,8 +210,6 @@ Let's create a `UserDetail` component so we can see some more information about Create a new file called `UserDetail.svelte` in the `/src` directory. In the component, we'll want an event dispatcher so that the parent component can listen for when we want to close this part of the UI. We'll also want a local function which calls the GitHub API for detailed user information. Finally, we'll want a template to display the info. - - ```svelte ``` - With the `UserDetails` type applied, we're now protected in the template. ## Current TypeScript Limitations in Svelte @@ -393,6 +356,7 @@ In our code above, we have a `closeDetails` event fired from the child `UserDeta /> // type error: closeTheDetails ``` + [This discussion](https://github.com/sveltejs/language-tools/issues/424) provides some insight on the topic and points to what we may see in the future for being able to type event handlers. ## Aside: Add Type-Safe Database Access with Prisma @@ -411,54 +375,40 @@ Let's start by creating a simple Node API. We'll build it with TypeScript. Start by creating a new folder and initiallizing npm. - - ```shell mkdir svelte-users-api cd svelte-users-api npm init -y ``` - Next, let's install the **dev** dependencies we'll need. - - ```shell npm install -D @prisma/cli typescript @types/node @types/express @types/cors ts-node-dev ``` - Most of the dependencies here are related to TypeScript but the first one in the list is `@prismac/cli`. This package will give us all the tooling we need for running `prisma` commands in our workspace to create our database models, run migrations, and more. Next, let's install our regular dependenceis. - - ```shell npm install @prisma/client express cors ``` - The first in the list for our regular dependencies is `@prisma/client`. The Prisma Client is what will give us type-safe access to our database. ### Initialize Prisma The Prisma CLI gives us an `init` command which takes care of creating a `/prisma` directory in our project and putting in a default model for us. Let's run that command and see what's inside. - - ```shell npx prisma init ``` - Inside the `/prisma` directory is a file called `schema.prisma`. This file uses the [Prisma Schema Language (PSL)](https://www.prisma.io/docs/concepts/components/prisma-schema#syntax) and is the place we describe all of our database tables and the relationships between them. Open up `schema.prisma` and put in a `User` model. This will represent a table which holds all of our user's data. - - ```prisma datasource db { provider = "sqlite" @@ -479,7 +429,6 @@ model User { } ``` - The `User` model has all of the same properties we were seeing from the GitHub API. This should allow us to easily swap out the calls to GitHub with calls to our own server. The `datasource db` line points to the database and connection we want to use. Prisma supports MySQL, Postgres, MSSQL, and SQLite. We'll use SQLite here as it's easy to work with and we don't need to spin anything else up. @@ -490,26 +439,20 @@ The `datasource db` line points to the database and connection we want to use. P With our model in place, we can now run a command to create and run a [migration](https://www.prisma.io/docs/concepts/components/prisma-migrate). - - ```shell npx prisma migrate dev --preview-feature ``` - The `prisma migrate` command will create a new `/prisma/migrations` directory in our project and will furnish it with the SQL needed to create our database table. It will then create the SQLite database in the `/prisma` directory and will create our `User` table. ### View the Database with Prisma Studio We can now view our database table by using [Prisma Studio](https://github.com/prisma/studio). - - ```shell npx prisma studio ``` - This will open up Prisma Studio in the browser at `http://locahost:5555`. ![Prisma Studio running at http:./imgs/svelte-typescript-6.png) @@ -524,29 +467,26 @@ With our database in place (and some data inside), we're now ready to create an Create a file at the project root called `server.ts`. This will be where we new up our Express app, our Prisma Client, and where we build out a `GET` endpoint for our data. - - ```ts -import express, { Request, Response } from 'express' -import cors from 'cors' -import { PrismaClient } from '@prisma/client' +import express, { Request, Response } from "express"; +import cors from "cors"; +import { PrismaClient } from "@prisma/client"; -const app = express() -app.use(cors()) +const app = express(); +app.use(cors()); -const prisma = new PrismaClient() +const prisma = new PrismaClient(); -app.get('/users', async (req: Request, res: Response) => { - const users = await prisma.user.findMany() - res.json(users) -}) +app.get("/users", async (req: Request, res: Response) => { + const users = await prisma.user.findMany(); + res.json(users); +}); -const PORT = process.env.PORT || 5001 -app.listen(PORT) -console.log(`Listening on http://localhost:${PORT}`) +const PORT = process.env.PORT || 5001; +app.listen(PORT); +console.log(`Listening on http://localhost:${PORT}`); ``` - When we want to access our database with Prisma, we use the `PrismaClient`. It's where we get the typings for our data models and the guarantees of type safety when accessing our database. The `prisma` variable that we've declared points to an instance of `PrismaClient`. On this instance we have access to a `user` property which points to our `User` model. We also get a number of methods that we can run on it to access the database, including a `findMany` records call, which we're using here. @@ -557,17 +497,12 @@ Let's now run the API and swap out the calls to GitHub for a call to our server. In the project for the API, use `ts-node-dev` to run the server. - - ```shell ts-node-dev server.ts ``` - In the Svelte project, let's swap our the github.com URL in the fetch call in `Users.svelte` with our API URL. - - ```svelte ``` - With this simple change, we should now be getting data from our server instead of from GitHub. ![Svelte app pulling user data from our own API](/build-an-app-with-svelte-and-typescript-PZDY3t93qAtd/imgs/svelte-typescript-8.png) @@ -597,4 +531,3 @@ With this simple change, we should now be getting data from our server instead o The type safety benefits that are now readily available in Svelte apps help to make our lives easier in the long run. Like any TypeScript project, it requires more effort upfront to make the app work. That extra effort pays dividends as time goes on since we can have more confidence about how our code works and we can catch bugs before they make it to production. Svelte and TypeScript pair up very nicely. Rounding things out with type safety on the backend using Prisma for database access makes for a compelling stack. - diff --git a/apps/blog/content/blog/build-applications-at-the-edge-with-prisma-orm-and-cloudflare-d1-preview/index.mdx b/apps/blog/content/blog/build-applications-at-the-edge-with-prisma-orm-and-cloudflare-d1-preview/index.mdx index 2d4a4d43f0..b1c6508d0d 100644 --- a/apps/blog/content/blog/build-applications-at-the-edge-with-prisma-orm-and-cloudflare-d1-preview/index.mdx +++ b/apps/blog/content/blog/build-applications-at-the-edge-with-prisma-orm-and-cloudflare-d1-preview/index.mdx @@ -20,7 +20,7 @@ Cloudflare has been pioneering the edge computing landscape since the introducti Edge functions, such as [Cloudflare Workers](https://workers.cloudflare.com/), are a form of lightweight serverless compute that's distributed across the globe. They allow you to deploy and run your apps as closely as possible to your end users. -[D1](https://developers.cloudflare.com/d1/) is Cloudflare's native serverless database for edge environments. It's based on SQLite and can be used when deploying applications with Cloudflare. D1 was [initially launched in 2022](https://blog.cloudflare.com/introducing-d1). +[D1](https://developers.cloudflare.com/d1/) is Cloudflare's native serverless database for edge environments. It's based on SQLite and can be used when deploying applications with Cloudflare. D1 was [initially launched in 2022](https://blog.cloudflare.com/introducing-d1). -You don't need to specify where a Cloudflare Worker or a D1 database runs—they simply run everywhere they need to. + You don't need to specify where a Cloudflare Worker or a D1 database runs—they simply run + everywhere they need to. - - -Following Cloudflare's principles of geographic distribution and bringing compute and data closer to application users, D1 supports automatic read-replication: It dynamically manages the number of database instances and locations of read-only replicas based on how many queries a database is getting, and from where. +Following Cloudflare's principles of geographic distribution and bringing compute and data closer to application users, D1 supports automatic read-replication: It dynamically manages the number of database instances and locations of read-only replicas based on how many queries a database is getting, and from where. This means that read-queries are executed against the D1 instance that's closest to the location from where the query was issued. @@ -44,16 +43,15 @@ For write-operations, on the other hand, queries still travel to a single primar ## Prisma ORM now supports D1 🚀 (Preview) -At Prisma, we believe that Cloudflare is at the forefront of building the future of how applications are being built and deployed. +At Prisma, we believe that Cloudflare is at the forefront of building the future of how applications are being built and deployed. > You can learn more about how we think about Cloudflare as a partner in improving [Data DX](https://www.datadx.io/) in this blog post: [Developer Experience Redefined: Prisma & Cloudflare Lead the Way to Data DX](https://www.prisma.io/blog/cloudflare-partnership-qerefgvwirjq) -> [Supporting D1](https://github.com/prisma/prisma/issues/13310) has been one of the most popular feature requests for Prisma ORM on GitHub. ![](/build-applications-at-the-edge-with-prisma-orm-and-cloudflare-d1-preview/imgs/dece55108fc5c399e5466b8dc5d73e8e9509cab0-1480x1696.png) -As a strong believer in Cloudflare as a technology provider, we're thrilled to share that you can now use Prisma ORM inside Cloudflare Workers (and Pages) to access a D1 database. +As a strong believer in Cloudflare as a technology provider, we're thrilled to share that you can now use Prisma ORM inside Cloudflare Workers (and Pages) to access a D1 database. Note that this feature is based on [driver adapters](https://www.prisma.io/docs/orm/overview/databases/database-drivers#driver-adapters) which are currently in Preview, we therefore consider D1 support to be in Preview as well. @@ -73,15 +71,15 @@ In the following, you'll find step-by-step instructions to set up and deploy a C As a first step, go ahead and use `npm create` to bootstrap a plain version of a Cloudflare Worker (using Cloudflare's [`hello-world`](https://github.com/cloudflare/workers-sdk/tree/4fdd8987772d914cf50725e9fa8cb91a82a6870d/packages/create-cloudflare/templates/hello-world) template). Run the following command in your terminal: ```shell -npm create cloudflare@latest prisma-d1-example -- --type hello-world +npm create cloudflare@latest prisma-d1-example -- --type hello-world ``` + This will bring up a CLI wizard. Select all the _default_ options by hitting **Return** every time a question appears. At the end of the wizard, you should have a deployed Cloudflare Worker at the domain `https://prisma-d1-example.USERNAME.workers.dev` which simply renders "Hello World" in the browser: ![](/build-applications-at-the-edge-with-prisma-orm-and-cloudflare-d1-preview/imgs/0464e1fee8099d997e95b6aaa24a13ef5d40cbb8-1950x1392.png) - ### 2. Initialize Prisma ORM With your Worker in place, let's go ahead and set up Prisma ORM. @@ -92,17 +90,20 @@ First, navigate into the project directory and install the Prisma CLI: cd prisma-d1-example npm install prisma --save-dev ``` + Next, install the Prisma Client package as well as the driver adapter for D1: ```shell npm install @prisma/client npm install @prisma/adapter-d1 ``` + Finally, bootstrap the files required by Prisma ORM using the following command: ```shell npx prisma init --datasource-provider sqlite ``` + This command did two things: - It created a new directory called `prisma` that contains your Prisma schema file. @@ -110,7 +111,7 @@ This command did two things: In this tutorial, you won't need the `.env` file since the connection between Prisma ORM and D1 will happen through a [binding](https://developers.cloudflare.com/workers/configuration/bindings/). You'll find instructions for setting up this binding in the next step. -Since you'll be using the driver adapter feature which is currently in Preview, you need to explicitly enable it via the `previewFeatures` field on the `generator` block. +Since you'll be using the driver adapter feature which is currently in Preview, you need to explicitly enable it via the `previewFeatures` field on the `generator` block. Open your `schema.prisma` file and adjust the `generator` block to look as follows: @@ -120,6 +121,7 @@ prisma generator client { previewFeatures = ["driverAdapters"] } ``` + ### 3. Create D1 database In this step, you'll set up your D1 database. There generally are two approaches to this. Either using the Cloudflare Dashboard UI or via the [`wrangler`](https://developers.cloudflare.com/workers/wrangler/) CLI. You'll use the CLI in this tutorial. @@ -129,6 +131,7 @@ Open your terminal and run the following command: ```shell npx wrangler d1 create prisma-demo-db ``` + If everything went well, you should see an output similar to this: ```shell @@ -141,6 +144,7 @@ binding = "DB" # i.e. available in your Worker on env.DB database_name = "prisma-demo-db" database_id = "__YOUR_D1_DATABASE_ID__" ``` + You now have a D1 database in your Cloudflare account with a binding to your Cloudflare Worker. Copy the last part of the command output and paste it into your `wrangler.toml` file. It should look similar to this: @@ -156,6 +160,7 @@ binding = "DB" # i.e. available in your Worker on env.DB database_name = "prisma-demo-db" database_id = "__YOUR_D1_DATABASE_ID__" ``` + Note that `__YOUR_D1_DATABASE_ID__` in the snippet above is a placeholder that should be replaced with the database ID of your own D1 instance. If you weren't able to grab this ID from the terminal output, you can also find it in the [Cloudflare Dashboard](https://dash.cloudflare.com/) or by running `npx wrangler d1 info prisma-demo-db` in your terminal. Next, you'll create a database table in the database in order to be able to send some queries to D1 using Prisma ORM. @@ -163,6 +168,7 @@ Next, you'll create a database table in the database in order to be able to send ### 4. Create a table in the database D1 comes with its own [migration system](https://developers.cloudflare.com/d1/reference/migrations) via the `wrangler d1 migrate` commands. This migration system plays nicely together with the Prisma CLI which provides tools that allow you to generate SQL statements for schema changes. So you can: + - use D1's native migration system to create and apply migration files to your D1 instance - use the Prisma CLI to generate the SQL statements for any schema changes @@ -173,6 +179,7 @@ First, create a new migration using the `wrangler` CLI: ```shell npx wrangler d1 migrations create prisma-demo-db create_user_table ``` + When prompted if the command can create a new folder called `migrations`, hit **Return** to confirm. The command has now created a new directory called `migrations` and an empty file called `0001_create_user_table.sql` inside of it: @@ -181,6 +188,7 @@ The command has now created a new directory called `migrations` and an empty fil migrations/ └── 0001_create_user_table.sql ``` + Next, you need to add the SQL statement that will create a `User` table to that file. Open the `schema.prisma` file and add the following `User` model to it: ```prisma @@ -190,11 +198,13 @@ model User { name String? } ``` + Now, run the following command in your terminal to generate the SQL statement that creates a `User` table equivalent to the `User` model above: ```shell npx prisma migrate diff --from-empty --to-schema-datamodel ./prisma/schema.prisma --script --output migrations/0001_create_user_table.sql ``` + This stores a SQL statement to create a new `User` table in your migration file `migrations/0001_ceate_user_table.sql` from before, here is what it looks like: ```sql @@ -208,12 +218,12 @@ CREATE TABLE "User" ( -- CreateIndex CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); ``` + You now need to use the `wrangler d1 migrations apply` command to send this SQL statement to D1. This command accepts two options: - `--local`: Executes the statement against a _local_ version of D1. This local version of D1 is a SQLite database file that'll be located in the `.wrangler/state` directory of your project. This approach is useful, when you want to develop and test your Worker on your local machine. Learn more in the [Cloudflare docs](https://developers.cloudflare.com/d1/configuration/local-development/). - `--remote`: Executes the statement against your _remote_ version of D1. This version is used by your _deployed_ Cloudflare Workers. Learn more in the [Cloudflare docs](https://developers.cloudflare.com/d1/configuration/remote-development/). - In this tutorial, you’ll do both: test the Worker locally _and_ deploy it afterwards. So, you need to run both commands. Open your terminal and paste the following commands. First, execute the schema changes against your _local_ database: @@ -221,11 +231,13 @@ First, execute the schema changes against your _local_ database: ```shell npx wrangler d1 migrations apply prisma-demo-db --local ``` + Next, against the remote database: ```shell npx wrangler d1 migrations apply prisma-demo-db --remote ``` + Hit **Return** both times when you're prompted to confirm that the migration should be applied. Both your local and remote D1 instances now contain `User` table. @@ -235,17 +247,18 @@ Let’s also create some dummy data that we can query once the Worker is running Again, run the command against your _local_ database first: ```shell -npx wrangler d1 execute prisma-demo-db --command "INSERT INTO \"User\" (\"email\", \"name\") VALUES +npx wrangler d1 execute prisma-demo-db --command "INSERT INTO \"User\" (\"email\", \"name\") VALUES ('jane@prisma.io', 'Jane Doe (Local)');" --local ``` + Finally, run it against your _remote_ database: ```shell -npx wrangler d1 execute prisma-demo-db --command "INSERT INTO \"User\" (\"email\", \"name\") VALUES +npx wrangler d1 execute prisma-demo-db --command "INSERT INTO \"User\" (\"email\", \"name\") VALUES ('jane@prisma.io', 'Jane Doe (Remote)');" --remote ``` -You now have a dummy record in both your local and remote database instances. You can find the local SQLite file in `.wrangler/state` while the remote one can be inspected in your Cloudflare Dashboard. +You now have a dummy record in both your local and remote database instances. You can find the local SQLite file in `.wrangler/state` while the remote one can be inspected in your Cloudflare Dashboard. ### 5. Query your database from the Worker @@ -258,29 +271,31 @@ In order to query your database from the Worker using Prisma ORM, you need to: Open `src/index.ts` and replace the entire content with the following: ```ts -import { PrismaClient } from '@prisma/client' -import { PrismaD1 } from '@prisma/adapter-d1' +import { PrismaClient } from "@prisma/client"; +import { PrismaD1 } from "@prisma/adapter-d1"; export interface Env { - DB: D1Database + DB: D1Database; } export default { - async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise { - const adapter = new PrismaD1(env.DB) - const prisma = new PrismaClient({ adapter }) + async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise { + const adapter = new PrismaD1(env.DB); + const prisma = new PrismaClient({ adapter }); - const users = await prisma.user.findMany() - const result = JSON.stringify(users) + const users = await prisma.user.findMany(); + const result = JSON.stringify(users); return new Response(result); - }, + }, }; ``` + Before running the Worker, you need to generate Prisma Client with the following command: ```shell npx prisma generate ``` + ### 6. Run the Worker locally With the database query in place and Prisma Client generated, you can go ahead and run the Worker locally: @@ -288,11 +303,13 @@ With the database query in place and Prisma Client generated, you can go ahead a ```shell npm run dev ``` + Now you can open your browser at [`http://localhost:8787`](http://localhost:8787/) to see the result of the database query: ```json -[{"id":1,"email":"jane@prisma.io","name":"Jane Doe (Local)"}] +[{ "id": 1, "email": "jane@prisma.io", "name": "Jane Doe (Local)" }] ``` + ### 7. Deploy the Worker To deploy the Worker, run the the following command: @@ -300,8 +317,8 @@ To deploy the Worker, run the the following command: ```shell npm run deploy ``` -As before, your deployed Worker is accessible via `https://prisma-d1-example.USERNAME.workers.dev`. If you navigate your browser to that URL, you should see the following data that's queried from your remote D1 database: +As before, your deployed Worker is accessible via `https://prisma-d1-example.USERNAME.workers.dev`. If you navigate your browser to that URL, you should see the following data that's queried from your remote D1 database: ![](/build-applications-at-the-edge-with-prisma-orm-and-cloudflare-d1-preview/imgs/a695edcc461f4bc01a9eac9d3a103d8ef2ab9dfd-1960x1386.png) diff --git a/apps/blog/content/blog/caching-database-queries-with-prisma-accelerate/index.mdx b/apps/blog/content/blog/caching-database-queries-with-prisma-accelerate/index.mdx index 938acabde2..34eda69161 100644 --- a/apps/blog/content/blog/caching-database-queries-with-prisma-accelerate/index.mdx +++ b/apps/blog/content/blog/caching-database-queries-with-prisma-accelerate/index.mdx @@ -8,7 +8,7 @@ metaTitle: "Cache your database queries with Prisma Accelerate" metaDescription: "Achieve faster performance and cost savings with Prisma Accelerate's easy per-query caching, eliminating the need for maintaining caching infrastructure. Experience faster queries, without the hassle!" metaImagePath: "/caching-database-queries-with-prisma-accelerate/imgs/meta-959743e064c1771e65e078aa6ebee2b402c28578-1266x711.png" heroImagePath: "/caching-database-queries-with-prisma-accelerate/imgs/hero-69cd8d277e2307eb14396450817be41bb1e7c305-844x474.svg" -heroImageAlt: "The word \"caching\" with a starburst behind it, showing that it is important." +heroImageAlt: 'The word "caching" with a starburst behind it, showing that it is important.' --- Dive into the benefits of per-query caching, showcasing how it can make queries faster, handle traffic surges, minimize infrastructure costs, and keep your users satisfied. Learn how to easily implement Prisma Accelerate and achieve improved app performance and cost savings. @@ -17,7 +17,7 @@ Picture this: You and your team just released your latest app, SuperWidget. Ever To solve this, your team jumps into action quickly. You dive into your application monitoring and realize that several queries in your application have a much larger impact than anticipated. After a long night, your team implements a number of infrastructure improvements, most notably a robust caching layer, which takes the load off the rest of your infrastructure. SuperWidget performance is no longer being negatively impacted, and your new customers are happy with their experience. -So, what could you and your team have done better? +So, what could you and your team have done better? While your team was capable, fire drills and all-nighters are the last thing you want. The solution still required a coordinated effort and a lot of wasted engineering hours. Instead, you could have saved time and frustration by using [Prisma Accelerate](https://www.prisma.io/data-platform/accelerate?utm_source=website&utm_medium=blogpost&utm_campaign=caching) and caching your queries with ease. @@ -42,13 +42,14 @@ const myExpensiveQuery = await prisma.widget.findMany({ swr: 60 * 2, // serve from cache for 2 more minutes and revalidate in the background }, // [...] -}) +}); ``` + In the first case, where expensive or frequently accessed queries can overwhelm your database, a per-query cache will prevent that load for as long as the data is cached. If cost is the concern, your data will be accessed from your database once and then cached in Accelerate’s collection of globally distributed nodes, preventing additional costs from further database reads while also making your app faster! ### When to cache -Now that you know why and how to cache, you may be tempted to begin caching every query. Before you do that, note what happened in our example: you and your team monitored your application *before* implementing caching. +Now that you know why and how to cache, you may be tempted to begin caching every query. Before you do that, note what happened in our example: you and your team monitored your application _before_ implementing caching. Caching is great, but any addition can incur a cost or cause unintended side effects. At Prisma, we’re big fans of observability-driven development: instrument your app and make informed decisions. Caching should be a carefully considered option among many. If, for example, you need a certain query to always contain exactly up-to-date data, then caching will probably not be the right fit. On the other hand, cases where data doesn’t change often or doesn’t need to be up-to-date are great fits. @@ -80,10 +81,11 @@ Accelerate is also a great fit for teams that want to onboard engineers fast. Si ### Wrapping up -Caching is still one of the “hard” problems of software engineering and rightfully so! Simply caching more things won’t solve problems and a team needs to take the time to understand what can be cached and how it will impact their product. +Caching is still one of the “hard” problems of software engineering and rightfully so! Simply caching more things won’t solve problems and a team needs to take the time to understand what can be cached and how it will impact their product. While there are many different approaches to caching, Prisma Accelerate makes it easy to implement caching on a pre-query basis and gives you more time to focus on building great products. If you’d like to take the next step, [learn more about Accelerate](https://www.prisma.io/data-platform/accelerate?utm_source=website&utm_medium=blogpost&utm_campaign=caching) and [get started](https://www.prisma.io/docs/accelerate/getting-started) caching your Prisma ORM queries today.

-[Start caching with Accelerate](https://console.prisma.io/login?utm_source=website&utm_medium=blogpost&utm_campaign=caching) +[Start caching with +Accelerate](https://console.prisma.io/login?utm_source=website&utm_medium=blogpost&utm_campaign=caching) diff --git a/apps/blog/content/blog/client-extensions-ga-4g4yIu8eOSbB/index.mdx b/apps/blog/content/blog/client-extensions-ga-4g4yIu8eOSbB/index.mdx index e333d3fd86..07c9c55e8f 100644 --- a/apps/blog/content/blog/client-extensions-ga-4g4yIu8eOSbB/index.mdx +++ b/apps/blog/content/blog/client-extensions-ga-4g4yIu8eOSbB/index.mdx @@ -28,34 +28,32 @@ If this is the first time you're hearing about Client extensions, don't worry. W This code snippet shows how you can add a _new method_ to the `User` model using a [`model`](https://www.prisma.io/docs/orm/prisma-client/client-extensions/model) extension: ```typescript -import { PrismaClient } from '@prisma/client'; +import { PrismaClient } from "@prisma/client"; const prisma = new PrismaClient().$extends({ model: { user: { - async signUp(email: string) { + async signUp(email: string) { // code for the new method goes inside the brackets - }, + }, }, }, }); // The new method can then be used like this: -const newUser = await prisma.user.signUp('myemail@email.com'); +const newUser = await prisma.user.signUp("myemail@email.com"); ``` + If you instead require a method on _all models_, you can even use the builtin `$allModels` feature: ```typescript -import { PrismaClient } from '@prisma/client'; +import { PrismaClient } from "@prisma/client"; const prisma = new PrismaClient().$extends({ model: { $allModels: { - async exists( - this: T, - where: Prisma.Args['where'] - ): Promise { + async exists(this: T, where: Prisma.Args["where"]): Promise { // code for the new method goes inside the brackets }, }, @@ -66,6 +64,7 @@ const prisma = new PrismaClient().$extends({ const postExists = await prisma.post.exists({ id: 1 }); ``` + For a more in-depth look into changes we made to the extensions API as a part of this release, please check out [our release notes](https://github.com/prisma/prisma/releases/tag/4.16.0) ### Extensions built by the community @@ -73,8 +72,8 @@ For a more in-depth look into changes we made to the extensions API as a part of While client extensions are now generally available, we have already seen some cool examples in the wild. [`prisma-extension-pagination`](https://github.com/deptyped/prisma-extension-pagination) is an awesome contribution from our community. Importing and using an external client extension is easy too: ```typescript -import { PrismaClient } from '@prisma/client'; -import pagination from 'prisma-extension-pagination'; +import { PrismaClient } from "@prisma/client"; +import pagination from "prisma-extension-pagination"; const prisma = new PrismaClient().$extends(pagination); @@ -82,12 +81,13 @@ const [users, meta] = prisma.user .paginate({ select: { id: true, - } + }, }) .withPages({ limit: 10, }); ``` + ### Reference examples for various use cases In addition to community contributions, we have a set of reference examples in the [`prisma-client-extensions` example repository](https://github.com/prisma/prisma-client-extensions) that showcase different areas where we believe Prisma Client extensions can be useful. The repository currently contains the following example extensions: @@ -99,7 +99,7 @@ In addition to community contributions, we have a set of reference examples in t | [computed-fields](https://github.com/prisma/prisma-client-extensions/tree/main/computed-fields) | Adds virtual / computed fields to result objects | | [input-transformation](https://github.com/prisma/prisma-client-extensions/tree/main/input-transformation) | Transforms the input arguments passed to Prisma Client queries to filter the result set | | [input-validation](https://github.com/prisma/prisma-client-extensions/tree/main/input-validation) | Runs custom validation logic on input arguments passed to mutation methods | -| [instance-methods](https://github.com/prisma/prisma-client-extensions/tree/main/instance-methods) | Adds Active Record-like methods like `save()` and `delete()` to result objects | +| [instance-methods](https://github.com/prisma/prisma-client-extensions/tree/main/instance-methods) | Adds Active Record-like methods like `save()` and `delete()` to result objects | | [json-field-types](https://github.com/prisma/prisma-client-extensions/tree/main/json-field-types) | Uses strongly-typed runtime parsing for data stored in JSON columns | | [model-filters](https://github.com/prisma/prisma-client-extensions/tree/main/model-filters) | Adds reusable filters that can composed into complex where conditions for a model | | [obfuscated-fields](https://github.com/prisma/prisma-client-extensions/tree/main/obfuscated-fields) | Prevents sensitive data (e.g. password fields) from being included in results | @@ -109,12 +109,12 @@ In addition to community contributions, we have a set of reference examples in t | [row-level-security](https://github.com/prisma/prisma-client-extensions/tree/main/row-level-security) | Uses Postgres row-level security policies to isolate data a multi-tenant application | | [static-methods](https://github.com/prisma/prisma-client-extensions/tree/main/static-methods) | Adds custom query methods to Prisma Client models | | [transformed-fields](https://github.com/prisma/prisma-client-extensions/tree/main/transformed-fields) | Demonstrates how to use result extensions to transform query results and add i18n to an app | -| [exists-fn](https://github.com/prisma/prisma-client-extensions/tree/main/exists-fn) | Demonstrates how to add an exists method to all your models | +| [exists-fn](https://github.com/prisma/prisma-client-extensions/tree/main/exists-fn) | Demonstrates how to add an exists method to all your models | ### Show off your extensions! -If you'd like a deeper dive into Prisma Client extensions, be sure to check out our previous write-up: [Prisma Client Just Became a Lot More Flexible: Prisma Client Extensions](/client-extensions-preview-8t3w27xkrxxn)! +If you'd like a deeper dive into Prisma Client extensions, be sure to check out our previous write-up: [Prisma Client Just Became a Lot More Flexible: Prisma Client Extensions](/client-extensions-preview-8t3w27xkrxxn)! -We'd also love to hear about your extensions (and maybe even take them for a spin). +We'd also love to hear about your extensions (and maybe even take them for a spin). Be sure to show of your `#MadeWithPrisma` work in our [Discord](https://discord.gg/KQyTW2H5ca) diff --git a/apps/blog/content/blog/client-extensions-preview-8t3w27xkrxxn/index.mdx b/apps/blog/content/blog/client-extensions-preview-8t3w27xkrxxn/index.mdx index f1153c6bae..7ba8e4a81a 100644 --- a/apps/blog/content/blog/client-extensions-preview-8t3w27xkrxxn/index.mdx +++ b/apps/blog/content/blog/client-extensions-preview-8t3w27xkrxxn/index.mdx @@ -58,12 +58,14 @@ generator client { previewFeatures = ["clientExtensions"] } ``` + Then, you can call the `$extends` method on a Prisma Client instance. This will return a new, "extended" client instance without modifying the original instance. You can chain calls to `$extends` to use multiple extensions, and create separate instances with different extensions: ```typescript const prisma = new PrismaClient(); const extendedPrisma = prisma.$extends(myExtensionA).$extends(myExtensionB); ``` + ### The components of an extension There are four different types of components that can be included in an extension: @@ -78,12 +80,21 @@ A single extension can include one or more components, as well as an optional na ```typescript const prisma = new PrismaClient().$extends({ name: "myExtension", - model: { /* ... */ }, - client: { /* ... */ }, - query: { /* ... */ }, - result: { /* ... */ }, + model: { + /* ... */ + }, + client: { + /* ... */ + }, + query: { + /* ... */ + }, + result: { + /* ... */ + }, }); ``` + To see the full syntax for defining each type of extension component, please refer to [the docs](https://www.prisma.io/docs/concepts/components/prisma-client/client-extensions). ### Sharing an extension @@ -102,6 +113,7 @@ export default Prisma.defineExtension({ }, }); ``` + When publishing shared extensions to npm, we recommend using the `prisma-extension-` convention. This will make it easier for users to find and install your extension in their apps. For example, if you publish an extension with the package name `prisma-extension-find-or-create`, users can install it like: @@ -109,6 +121,7 @@ For example, if you publish an extension with the package name `prisma-extension ```sh npm install prisma-extension-find-or-create ``` + And then use the extension in their app: ```typescript @@ -116,8 +129,11 @@ import { PrismaClient } from "@prisma/client"; import findOrCreate from "prisma-extension-find-or-create"; const prisma = new PrismaClient().$extends(findOrCreate); -const user = await prisma.user.findOrCreate({ /* ... */ }); +const user = await prisma.user.findOrCreate({ + /* ... */ +}); ``` + Read [our documentation on sharing extensions](https://www.prisma.io/docs/concepts/components/prisma-client/client-extensions/shared-extensions) for more details. ## Sample use cases @@ -140,7 +156,7 @@ Computed fields are type-safe and may return anything from simple values to comp import { PrismaClient } from "@prisma/client"; const prisma = new PrismaClient() - .$extends({ +.$extends({ result: { user: { fullName: { @@ -153,25 +169,27 @@ const prisma = new PrismaClient() }, }) .$extends({ - result: { - user: { - displayName: { - needs: { fullName: true, email: true }, - compute(user) { - return `${user.fullName} <${user.email}>`; - }, - }, - }, - }, - }); -``` +result: { +user: { +displayName: { +needs: { fullName: true, email: true }, +compute(user) { +return `${user.fullName} <${user.email}>`; +}, +}, +}, +}, +}); + +```` ```typescript const users = await prisma.user.findMany({ take: 5 }); for (const user of users) { console.info(`- ${user.displayName}`); } -``` +```` + ```prisma model User { id String @id @default(cuid()) @@ -180,6 +198,7 @@ model User { email String } ``` + @@ -199,28 +218,30 @@ import { de } from "date-fns/locale"; import { PrismaClient } from "@prisma/client"; const prisma = new PrismaClient().$extends({ - result: { - post: { - createdAt: { - needs: { createdAt: true }, - compute(post) { - return formatDistanceToNow(post.createdAt, { - addSuffix: true, - locale: de, - }); - }, - }, - }, - }, +result: { +post: { +createdAt: { +needs: { createdAt: true }, +compute(post) { +return formatDistanceToNow(post.createdAt, { +addSuffix: true, +locale: de, }); -``` +}, +}, +}, +}, +}); + +```` ```typescript const posts = await prisma.post.findMany({ take: 5 }); for (const post of posts) { console.info(`- ${post.title} (${post.createdAt})`); } -``` +```` + ```prisma model Post { id String @id @default(cuid()) @@ -228,6 +249,7 @@ model Post { createdAt DateTime @default(now()) } ``` + @@ -243,23 +265,25 @@ This example is a special case for the previous [Transformed fields](#example-tr import { PrismaClient } from "@prisma/client"; const prisma = new PrismaClient().$extends({ - result: { - user: { - password: { - needs: {}, - compute() { - return undefined; - }, - }, - }, - }, +result: { +user: { +password: { +needs: {}, +compute() { +return undefined; +}, +}, +}, +}, }); -``` + +```` ```typescript const user = await prisma.user.findFirstOrThrow(); console.info("Email: ", user.email); // "user@example.com" console.info("Password: ", user.password); // undefined -``` +```` + ```prisma model User { id String @id @default(cuid()) @@ -267,6 +291,7 @@ model User { password String } ``` + @@ -284,14 +309,14 @@ This technique can be used to customize Prisma result objects with behavior, ana import { PrismaClient } from "@prisma/client"; const prisma = new PrismaClient().$extends({ - result: { - user: { - save: { - needs: { id: true, email: true }, - compute({ id, email }) { - return () => prisma.user.update({ where: { id }, data: { email } }); - }, - }, +result: { +user: { +save: { +needs: { id: true, email: true }, +compute({ id, email }) { +return () => prisma.user.update({ where: { id }, data: { email } }); +}, +}, delete: { needs: { id: true }, @@ -300,9 +325,11 @@ const prisma = new PrismaClient().$extends({ }, }, }, - }, + +}, }); -``` + +```` ```typescript const user = await prisma.user.create({ data: { email: "test@example.com" }, @@ -310,13 +337,15 @@ const user = await prisma.user.create({ user.email = "updated@example.com"; await user.save(); await user.delete(); -``` +```` + ```prisma model User { id String @id @default(cuid()) email String } ``` + @@ -335,21 +364,21 @@ import bcrypt from "bcryptjs"; import { PrismaClient } from "@prisma/client"; const prisma = new PrismaClient().$extends({ - model: { - user: { - async signUp(email: string, password: string) { - const hash = await bcrypt.hash(password, 10); - return prisma.user.create({ - data: { - email, - password: { - create: { - hash, - }, - }, - }, - }); - }, +model: { +user: { +async signUp(email: string, password: string) { +const hash = await bcrypt.hash(password, 10); +return prisma.user.create({ +data: { +email, +password: { +create: { +hash, +}, +}, +}, +}); +}, async findManyByDomain(domain: string) { return prisma.user.findMany({ @@ -357,15 +386,18 @@ const prisma = new PrismaClient().$extends({ }); }, }, - }, + +}, }); -``` + +```` ```typescript await prisma.user.signUp("user1@example1.com", "p4ssword"); await prisma.user.signUp("user2@example2.com", "s3cret"); const users = await prisma.user.findManyByDomain("example2.com"); -``` +```` + ```prisma model User { id String @id @default(cuid()) @@ -379,6 +411,7 @@ model Password { userId String @unique } ``` + @@ -401,16 +434,17 @@ const prisma = new PrismaClient().$extends({ byAuthor: (authorId: string) => ({ authorId }), byAuthorDomain: (domain: string) => ({ author: { email: { endsWith: `@${domain}` } }, - }), - hasComments: () => ({ comments: { some: {} } }), - hasRecentComments: (date: Date) => ({ - comments: { some: { createdAt: { gte: date } } }, - }), - titleContains: (search: string) => ({ title: { contains: search } }), - } satisfies Record Prisma.PostWhereInput>, - }, +}), +hasComments: () => ({ comments: { some: {} } }), +hasRecentComments: (date: Date) => ({ +comments: { some: { createdAt: { gte: date } } }, +}), +titleContains: (search: string) => ({ title: { contains: search } }), +} satisfies Record Prisma.PostWhereInput>, +}, }); -``` + +```` ```typescript const posts = await prisma.post.findMany({ where: { @@ -422,7 +456,8 @@ const posts = await prisma.post.findMany({ ], }, }); -``` +```` + ```prisma model User { id String @id @default(cuid()) @@ -452,6 +487,7 @@ model Comment { post Post @relation(fields: [postId], references: [id], onDelete: Cascade) } ``` + @@ -467,38 +503,38 @@ This example creates a client that only allows read operations like `findMany` a import { Prisma, PrismaClient } from "@prisma/client"; const WRITE_METHODS = [ - "create", - "update", - "upsert", - "delete", - "createMany", - "updateMany", - "deleteMany", +"create", +"update", +"upsert", +"delete", +"createMany", +"updateMany", +"deleteMany", ] as const; const ReadonlyClient = Prisma.defineExtension({ - name: "ReadonlyClient", - model: { - $allModels: Object.fromEntries( +name: "ReadonlyClient", +model: { +$allModels: Object.fromEntries( WRITE_METHODS.map((method) => [ method, function (args: never) { throw new Error( - `Calling the \`${method}\` method on a readonly client is not allowed` - ); + `Calling the \`${method}\` method on a readonly client is not allowed` ); }, ]) ) as { [K in typeof WRITE_METHODS[number]]: ( - args: `Calling the \`${K}\` method on a readonly client is not allowed` - ) => never; - }, - }, + args:`Calling the \`${K}\` method on a readonly client is not allowed` +) => never; +}, +}, }); const prisma = new PrismaClient(); const readonlyPrisma = prisma.$extends(ReadonlyClient); -``` + +```` ```typescript const posts = await readonlyPrisma.post.findMany({ take: 5 }); console.log(posts); @@ -510,7 +546,8 @@ console.log(posts); await readonlyPrisma.post.create({ data: { title: "New post", published: false }, }); -``` +```` + ```prisma model Post { id String @id @default(cuid()) @@ -518,10 +555,10 @@ model Post { published Boolean } ``` + - ### Example: Input transformation [View full example on GitHub](https://github.com/prisma/prisma-client-extensions/tree/main/input-transformation) @@ -555,12 +592,15 @@ const prisma = new PrismaClient().$extends({ }); }, }, - }, + +}, }); -``` + +```` ```typescript const publishedPostsCount = await prisma.post.count(); -``` +```` + ```prisma model Post { id String @id @default(cuid()) @@ -568,6 +608,7 @@ model Post { published Boolean } ``` + @@ -586,29 +627,30 @@ import { Prisma, PrismaClient } from "@prisma/client"; import { ProductCreateInput } from "./schemas"; const prisma = new PrismaClient().$extends({ - query: { - product: { - create({ args, query }) { - args.data = ProductCreateInput.parse(args.data); - return query(args); - }, - update({ args, query }) { - args.data = ProductCreateInput.partial().parse(args.data); - return query(args); - }, - updateMany({ args, query }) { - args.data = ProductCreateInput.partial().parse(args.data); - return query(args); - }, - upsert({ args, query }) { - args.create = ProductCreateInput.parse(args.create); - args.update = ProductCreateInput.partial().parse(args.update); - return query(args); - }, - }, - }, +query: { +product: { +create({ args, query }) { +args.data = ProductCreateInput.parse(args.data); +return query(args); +}, +update({ args, query }) { +args.data = ProductCreateInput.partial().parse(args.data); +return query(args); +}, +updateMany({ args, query }) { +args.data = ProductCreateInput.partial().parse(args.data); +return query(args); +}, +upsert({ args, query }) { +args.create = ProductCreateInput.parse(args.create); +args.update = ProductCreateInput.partial().parse(args.update); +return query(args); +}, +}, +}, }); -``` + +```` ```typescript import { z } from "zod"; import { Prisma } from "@prisma/client"; @@ -624,7 +666,8 @@ export const ProductCreateInput = z.object({ .instanceof(Prisma.Decimal) .refine((price) => price.gte("0.01") && price.lt("1000000.00")), }) satisfies z.Schema; -``` +```` + ```typescript // Valid product const product = await prisma.product.create({ @@ -650,6 +693,7 @@ try { console.log(err?.cause?.issues); } ``` + ```prisma model Product { id String @id @default(cuid()) @@ -668,6 +712,7 @@ model Review { productId String } ``` + @@ -689,53 +734,54 @@ import { Prisma, PrismaClient } from "@prisma/client"; import { Profile } from "./schemas"; const prisma = new PrismaClient().$extends({ - result: { - user: { - profile: { - needs: { profile: true }, - compute({ profile }) { - return Profile.parse(profile); - }, - }, - }, - }, - - query: { - user: { - create({ args, query }) { - args.data.profile = Profile.parse(args.data.profile); - return query(args); - }, - createMany({ args, query }) { - const users = Array.isArray(args.data) ? args.data : [args.data]; - for (const user of users) { - user.profile = Profile.parse(user.profile); - } - return query(args); - }, - update({ args, query }) { - if (args.data.profile !== undefined) { - args.data.profile = Profile.parse(args.data.profile); - } - return query(args); - }, - updateMany({ args, query }) { - if (args.data.profile !== undefined) { - args.data.profile = Profile.parse(args.data.profile); - } - return query(args); - }, - upsert({ args, query }) { - args.create.profile = Profile.parse(args.create.profile); - if (args.update.profile !== undefined) { - args.update.profile = Profile.parse(args.update.profile); - } - return query(args); - }, - }, - }, +result: { +user: { +profile: { +needs: { profile: true }, +compute({ profile }) { +return Profile.parse(profile); +}, +}, +}, +}, + +query: { +user: { +create({ args, query }) { +args.data.profile = Profile.parse(args.data.profile); +return query(args); +}, +createMany({ args, query }) { +const users = Array.isArray(args.data) ? args.data : [args.data]; +for (const user of users) { +user.profile = Profile.parse(user.profile); +} +return query(args); +}, +update({ args, query }) { +if (args.data.profile !== undefined) { +args.data.profile = Profile.parse(args.data.profile); +} +return query(args); +}, +updateMany({ args, query }) { +if (args.data.profile !== undefined) { +args.data.profile = Profile.parse(args.data.profile); +} +return query(args); +}, +upsert({ args, query }) { +args.create.profile = Profile.parse(args.create.profile); +if (args.update.profile !== undefined) { +args.update.profile = Profile.parse(args.update.profile); +} +return query(args); +}, +}, +}, }); -``` + +```` ```typescript import { z } from "zod"; @@ -788,7 +834,8 @@ export const Profile = z socialLinks: SocialLinks, }) .partial(); -``` +```` + ```typescript import * as runtime from "@prisma/client/runtime/index"; import { UserPayload } from "@prisma/client"; @@ -796,13 +843,14 @@ import { UserPayload } from "@prisma/client"; const users = await prisma.user.findMany({ take: 10 }); users.forEach(renderUser); -type ExtArgs = UserPayload; +type ExtArgs = UserPayload<(typeof prisma)["$extends"]["extArgs"]>; type User = runtime.Types.GetResult; function renderUser(user: User) { // ... } ``` + ```prisma model User { id String @id @default(cuid()) @@ -810,6 +858,7 @@ model User { profile Json } ``` + @@ -831,25 +880,26 @@ import { performance } from "perf_hooks"; import * as util from "util"; const prisma = new PrismaClient().$extends({ - query: { - $allModels: { - async $allOperations({ operation, model, args, query }) { - const start = performance.now(); - const result = await query(args); - const end = performance.now(); - const time = end - start; - console.log( - util.inspect( - { model, operation, args, time }, - { showHidden: false, depth: null, colors: true } - ) - ); - return result; - }, - }, - }, +query: { +$allModels: { +async $allOperations({ operation, model, args, query }) { +const start = performance.now(); +const result = await query(args); +const end = performance.now(); +const time = end - start; +console.log( +util.inspect( +{ model, operation, args, time }, +{ showHidden: false, depth: null, colors: true } +) +); +return result; +}, +}, +}, }); -``` + +```` ```typescript await prisma.user.findMany({ orderBy: [{ lastName: "asc" }, { firstName: "asc" }], @@ -857,7 +907,8 @@ await prisma.user.findMany({ }); await prisma.user.groupBy({ by: ["lastName"], _count: true }); -``` +```` + ```prisma model User { id String @id @default(cuid()) @@ -865,6 +916,7 @@ model User { lastName String } ``` + @@ -881,31 +933,32 @@ import { backOff, IBackOffOptions } from "exponential-backoff"; import { Prisma, PrismaClient } from "@prisma/client"; function RetryTransactions(options?: Partial) { - return Prisma.defineExtension((prisma) => - prisma.$extends({ +return Prisma.defineExtension((prisma) => +prisma.$extends({ client: { $transaction(...args: any) { return backOff(() => prisma.$transaction.apply(prisma, args), { - retry: (e) => { - // Retry the transaction only if the error was due to a write conflict or deadlock - // See: https://www.prisma.io/docs/reference/api-reference/error-reference#p2034 - return e.code === "P2034"; - }, - ...options, - }); - }, - } as { $transaction: typeof prisma["$transaction"] }, - }) - ); +retry: (e) => { +// Retry the transaction only if the error was due to a write conflict or deadlock +// See: https://www.prisma.io/docs/reference/api-reference/error-reference#p2034 +return e.code === "P2034"; +}, +...options, +}); +}, +} as { $transaction: typeof prisma["$transaction"] }, +}) +); } const prisma = new PrismaClient().$extends( - RetryTransactions({ - jitter: "full", - numOfAttempts: 5, - }) +RetryTransactions({ +jitter: "full", +numOfAttempts: 5, +}) ); -``` + +```` ```typescript // If one or more transactions fail due to deadlock / write conflict, // the entire transaction will be retried up to 5 times. @@ -920,7 +973,8 @@ await Promise.allSettled([ // ... }), ]); -``` +```` + ```prisma model User { id String @id @default(cuid()) @@ -928,6 +982,7 @@ model User { lastName String } ``` + @@ -945,19 +1000,19 @@ This gives you the full power of [interactive transactions](https://www.prisma.i import { Prisma, PrismaClient } from "@prisma/client"; type FlatTransactionClient = Prisma.TransactionClient & { - $commit: () => Promise; - $rollback: () => Promise; +$commit: () => Promise; +$rollback: () => Promise; }; const ROLLBACK = { [Symbol.for("prisma.client.extension.rollback")]: true }; const prisma = new PrismaClient().$extends({ - client: { - async $begin() { - const prisma = Prisma.getExtensionContext(this); - let setTxClient: (txClient: Prisma.TransactionClient) => void; - let commit: () => void; - let rollback: () => void; +client: { +async $begin() { +const prisma = Prisma.getExtensionContext(this); +let setTxClient: (txClient: Prisma.TransactionClient) => void; +let commit: () => void; +let rollback: () => void; // a promise for getting the tx inner client const txClient = new Promise((res) => { @@ -1006,15 +1061,18 @@ const prisma = new PrismaClient().$extends({ throw new Error("Transactions are not supported by this client"); }, - }, + +}, }); -``` + +```` ```typescript const tx = await prisma.$begin(); const user = await tx.user.findFirstOrThrow(); await tx.user.update(/* ... */); await tx.$commit(); // Or: await tx.$rollback(); -``` +```` + ```prisma model User { id String @id @default(cuid()) @@ -1023,6 +1081,7 @@ model User { email String } ``` + @@ -1040,23 +1099,24 @@ A detailed explanation of this solution can be found in [the example's `README` import { Prisma, PrismaClient } from "@prisma/client"; function forUser(userId: number) { - return Prisma.defineExtension((prisma) => - prisma.$extends({ +return Prisma.defineExtension((prisma) => +prisma.$extends({ query: { $allModels: { async $allOperations({ args, query }) { const [, result] = await prisma.$transaction([ - prisma.$executeRaw`SELECT set_config('app.current_user_id', ${userId.toString()}, TRUE)`, - query(args), - ]); - return result; - }, - }, - }, - }) - ); +prisma.$executeRaw`SELECT set_config('app.current_user_id', ${userId.toString()}, TRUE)`, +query(args), +]); +return result; +}, +}, +}, +}) +); } -``` + +```` ```typescript const prisma = new PrismaClient(); const user = await prisma.user.findFirstOrThrow(); @@ -1068,7 +1128,8 @@ await userPrisma.product.update({ where: { id: product.id }, data: { name: "Updated Name" }, }); -``` +```` + ```prisma model User { id Int @id @default(autoincrement()) @@ -1114,6 +1175,7 @@ model ProductVersion { @@schema("audit") } ``` + ```sql -- Product audit trigger function CREATE OR REPLACE FUNCTION "audit"."Product_audit"() RETURNS TRIGGER AS $$ @@ -1136,6 +1198,7 @@ CREATE TRIGGER audit AFTER INSERT OR UPDATE OR DELETE ON "public"."Product" FOR EACH ROW EXECUTE FUNCTION "audit"."Product_audit"(); ``` + @@ -1153,41 +1216,42 @@ A detailed explanation of this solution can be found in [the example's `README` import { Prisma, PrismaClient } from "@prisma/client"; function bypassRLS() { - return Prisma.defineExtension((prisma) => - prisma.$extends({ +return Prisma.defineExtension((prisma) => +prisma.$extends({ query: { $allModels: { async $allOperations({ args, query }) { const [, result] = await prisma.$transaction([ - prisma.$executeRaw`SELECT set_config('app.bypass_rls', 'on', TRUE)`, - query(args), - ]); - return result; - }, - }, - }, - }) - ); +prisma.$executeRaw`SELECT set_config('app.bypass_rls', 'on', TRUE)`, +query(args), +]); +return result; +}, +}, +}, +}) +); } function forCompany(companyId: string) { - return Prisma.defineExtension((prisma) => - prisma.$extends({ +return Prisma.defineExtension((prisma) => +prisma.$extends({ query: { $allModels: { async $allOperations({ args, query }) { const [, result] = await prisma.$transaction([ - prisma.$executeRaw`SELECT set_config('app.current_company_id', ${companyId}, TRUE)`, - query(args), - ]); - return result; - }, - }, - }, - }) - ); +prisma.$executeRaw`SELECT set_config('app.current_company_id', ${companyId}, TRUE)`, +query(args), +]); +return result; +}, +}, +}, +}) +); } -``` + +```` ```typescript const prisma = new PrismaClient(); const user = await prisma.$extends(bypassRLS()).user.findFirstOrThrow(); @@ -1205,7 +1269,8 @@ const projects = await companyPrisma.project.findMany({ }); invariant(projects.every((project) => project.companyId === user.companyId)); -``` +```` + ```prisma model Company { id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid @@ -1257,6 +1322,7 @@ model Task { assignee User? @relation(fields: [userId], references: [id], onDelete: SetNull) } ``` + ```sql -- Enable Row Level Security ALTER TABLE "Company" ENABLE ROW LEVEL SECURITY; @@ -1282,6 +1348,7 @@ CREATE POLICY bypass_rls_policy ON "User" USING (current_setting('app.bypass_rls CREATE POLICY bypass_rls_policy ON "Project" USING (current_setting('app.bypass_rls', TRUE)::text = 'on'); CREATE POLICY bypass_rls_policy ON "Task" USING (current_setting('app.bypass_rls', TRUE)::text = 'on'); ``` + diff --git a/apps/blog/content/blog/cloud-connectivity-report-2024/index.mdx b/apps/blog/content/blog/cloud-connectivity-report-2024/index.mdx index 41f48400d4..6d40e1e36e 100644 --- a/apps/blog/content/blog/cloud-connectivity-report-2024/index.mdx +++ b/apps/blog/content/blog/cloud-connectivity-report-2024/index.mdx @@ -14,11 +14,12 @@ The Prisma Cloud Connectivity Report 2024 analyzes network latency between AWS r At Prisma, we are operating services in most AWS regions and all Cloudflare PoPs outside of China. As a result, we have an extensive set of latency data for requests between Cloudflare and AWS. This question on X prompted us to start releasing this data in a yearly Cloud Connectivity Report: - + We collect p50, p90, p95, p99 and p999 latency metrics for the intersection of 16 AWS regions and 266 Cloudflare points of presence (PoPs) for a total of 21k data points. This report will focus on a few key data points, and the full dataset is linked at the end. ## AWS regions with the fastest connectivity to Cloudflare + The three AWS regions with the lowest latency to a nearby Cloudflare PoP are all in Asia Pacific: ![](/cloud-connectivity-report-2024/imgs/8eb86e862d6739302c7bc6209ef01b2f157146f3-1480x302.png) diff --git a/apps/blog/content/blog/cloudflare-partnership-qerefgvwirjq/index.mdx b/apps/blog/content/blog/cloudflare-partnership-qerefgvwirjq/index.mdx index eb1ad9efb4..920651bb52 100644 --- a/apps/blog/content/blog/cloudflare-partnership-qerefgvwirjq/index.mdx +++ b/apps/blog/content/blog/cloudflare-partnership-qerefgvwirjq/index.mdx @@ -53,4 +53,3 @@ This partnership further underscores Cloudflare's commitment to spearheading tec The remarkable response to Accelerate has left us thrilled. Inspired by this, our vision for [Data DX](https://datadx.io) will drive us to introduce more exciting products in this category. Don’t miss out on our upcoming product releases; [subscribe now](https://www.prisma.io/blog) and be the first to know when we share more news! We want to express our gratitude to our community for your invaluable feedback and collaboration. Your inputs have shaped our Data DX innovations, and we appreciate your support as we continue to unveil future products. There’s a lot more that we have under wraps that we are excited to bring to you over the coming months. Your support makes you the best community any technology company could wish for. Thank you from the bottom of our hearts! - diff --git a/apps/blog/content/blog/cloudflare-unikernels-and-bare-metal-life-of-a-prisma-postgres-query/index.mdx b/apps/blog/content/blog/cloudflare-unikernels-and-bare-metal-life-of-a-prisma-postgres-query/index.mdx index ee85a0080a..498c10d465 100644 --- a/apps/blog/content/blog/cloudflare-unikernels-and-bare-metal-life-of-a-prisma-postgres-query/index.mdx +++ b/apps/blog/content/blog/cloudflare-unikernels-and-bare-metal-life-of-a-prisma-postgres-query/index.mdx @@ -48,11 +48,11 @@ In the next sections, we'll take a closer look at each stage and explain what's ### Stage 1: It all starts with Prisma ORM -[Prisma ORM](https://www.prisma.io/orm) is where the journey of a Prisma Postgres query naturally starts. +[Prisma ORM](https://www.prisma.io/orm) is where the journey of a Prisma Postgres query naturally starts. #### No query engine needed on the application server with Prisma Postgres -If you've used Prisma ORM in the past, you may be aware that it uses a _Query Engine_ that's implemented in Rust and which runs as a binary on your application server. +If you've used Prisma ORM in the past, you may be aware that it uses a _Query Engine_ that's implemented in Rust and which runs as a binary on your application server. > Note that while we're still talking about the Query Engine being written in Rust in this context, it is currently being rewritten in TypeScript. Learn more [here](https://t.co/JJnKm2Fs7y). @@ -75,15 +75,16 @@ const users = await prisma.post.findMany({ cacheStrategy: { ttl: 60, swr: 30, - } -}) + }, +}); ``` + This query fetches all published posts from the database and additionally specifies two parameters that will be relevant for the Prisma Postgres cache: -- [Time-To-Live](https://www.prisma.io/docs/accelerate/caching#time-to-live-ttl) (`ttl`): Determines how long cached data is considered _fresh_. When you set a TTL value, Prisma Postgres will serve the cached data for that duration without querying the database. +- [Time-To-Live](https://www.prisma.io/docs/accelerate/caching#time-to-live-ttl) (`ttl`): Determines how long cached data is considered _fresh_. When you set a TTL value, Prisma Postgres will serve the cached data for that duration without querying the database. - [Stale-While-Revalidate](https://www.prisma.io/docs/accelerate/caching#stale-while-revalidate-swr) (`swr`): Allows Prisma Postgres to serve _stale_ cached data while fetching fresh data in the background. When you set an SWR value, Prisma Postgres will continue to serve the cached data for that duration, even if it's past the TTL, while simultaneously updating the cache with new data from the database. -In this example, the data will be considered fresh for 30 seconds (TTL). After that, for the next 60 seconds (SWR), Prisma Postgres's cache will serve the stale data while fetching fresh data in the background. +In this example, the data will be considered fresh for 30 seconds (TTL). After that, for the next 60 seconds (SWR), Prisma Postgres's cache will serve the stale data while fetching fresh data in the background. Prisma Postgres serves cached data from an edge location close to your application. If you deploy your app to multiple locations, this global cache can dramatically improve the performance of your app! @@ -107,17 +108,20 @@ So, when the query above is executed in an application, what happens next? Becau } } ``` -Note that the `selection` argument specifies the fields that should be fetched from the database. Since we didn't use a `select` or `include` option on our query, its value simply is `"$scalars": true` which means that all scalar fields of the target model will be return from the database. + +Note that the `selection` argument specifies the fields that should be fetched from the database. Since we didn't use a `select` or `include` option on our query, its value simply is `"$scalars": true` which means that all scalar fields of the target model will be return from the database. The next step for the request to be evaluated by Prisma Postgres' authentication and cache layers. ### Stage 2: Authenticating the request Before hitting the cache to check if the query result can be served from there, the query needs to be authenticated. A Prisma Postgres URL always contains an `apiKey` argument that encodes the user credentials: + ``` prisma+postgres://accelerate.prisma-data.net/?api_key=ey... ``` -The auth layer is implemented using Cloudflare Workers, and therefore in close physical proximity to the origin of the query. It uses the `apiKey` value to identify the user, validate the access permissions and route the request to the next stage. + +The auth layer is implemented using Cloudflare Workers, and therefore in close physical proximity to the origin of the query. It uses the `apiKey` value to identify the user, validate the access permissions and route the request to the next stage. ### Stage 3: To cache or not to cache @@ -132,9 +136,9 @@ The main purpose of this routing layer is to determine whether the Prisma Postgr So, let's investigate the path through Prisma Postgres' caching layer. -Being built on top of Cloudflare Workers, the Prisma Postgres cache takes advantage of the official [Cloudflare Cache API](https://developers.cloudflare.com/workers/runtime-apis/cache/). +Being built on top of Cloudflare Workers, the Prisma Postgres cache takes advantage of the official [Cloudflare Cache API](https://developers.cloudflare.com/workers/runtime-apis/cache/). -As a cache key, it uses a hash that's computed based on the _entire_ Prisma ORM query (including values for the query parameters, such as `published: true` in the case above). This approach errs on the side of cache misses and only returns data from the cache if there's a 100% certainty that this exact query was sent to the database and that its result was cached before. +As a cache key, it uses a hash that's computed based on the _entire_ Prisma ORM query (including values for the query parameters, such as `published: true` in the case above). This approach errs on the side of cache misses and only returns data from the cache if there's a 100% certainty that this exact query was sent to the database and that its result was cached before. You can see the statistics of your caching behavior in the Prisma Postgres dashboard: @@ -154,7 +158,7 @@ These VMs host the Query Engine that was moved out of the application server (as ### Stage 5: Entering the unikernel database -Prisma Postgres is based on _unikernels_ (think: "hyper-specialized operating systems") running as ultra-lightweight microVMs on our own bare metal servers. +Prisma Postgres is based on *unikernels* (think: "hyper-specialized operating systems") running as ultra-lightweight microVMs on our own bare metal servers. > Check out the [Early Access announcement](https://www.prisma.io/blog/announcing-prisma-postgres-early-access#building-a-managed-postgresql-service-on-millisecond-cloud-infrastructure) to learn about the details of that architecture, our collaboration with [Unikraft](https://unikraft.cloud/), and the millisecond cloud stack that enables the performance benefits of Prisma Postgres. @@ -172,7 +176,7 @@ Thanks to this highly efficient stack, a database instance incurs costs only whe Back to our query: After the initial JSON representation of the query has been transformed into an efficient SQL statement, the query finally reaches the database layer via TCP. The PostgreSQL instances are deployed using Unikraft's millisecond cloud stack on our own bare metal servers in close proximity to the connection pool. -The first stop here is the [Unikraft proxy](https://unikraft.cloud/how-it-works/#control-plane) which is responsible for maintaining the TCP connections to the connection pool. +The first stop here is the [Unikraft proxy](https://unikraft.cloud/how-it-works/#control-plane) which is responsible for maintaining the TCP connections to the connection pool. The proxy now talks to the Unikraft controller which is responsible for managing the _actual_ Prisma Postgres instances. At this point, there are two possible states: @@ -183,13 +187,13 @@ Don't get confused by the "pause" and "wake up" terminology here. Thanks to the ## What's next for the Prisma Postgres architecture? -While we've seen a lot of excitement about the current technology stack and the benefits it provides to developers already, we are not going to stop here! +While we've seen a lot of excitement about the current technology stack and the benefits it provides to developers already, we are not going to stop here! There are a number of additional optimizations that we see possible in future iterations of Prisma Postgres, most notably: We are going to move the connection pool onto the _same machines_ that are running the Prisma Postgres instances: ![Prisma Postgres components](/cloudflare-unikernels-and-bare-metal-life-of-a-prisma-postgres-query/imgs/b38aa9235dab3c4bbef178b0944da6f97a9c2c2f-660x852.png) -The TCP connection is the most expensive part in the entire stack, due to the three-way handshake that needs to be done every time a connection is established. By reducing this TCP connection to a merely local one happening between two processes on the same machine, the latency caused by the physical distance between the connection pool and database instance would become entirely negligible. +The TCP connection is the most expensive part in the entire stack, due to the three-way handshake that needs to be done every time a connection is established. By reducing this TCP connection to a merely local one happening between two processes on the same machine, the latency caused by the physical distance between the connection pool and database instance would become entirely negligible. This is the core advantage of Prisma Postgres compared to other providers that are based on AWS (or another cloud provider's) infrastructure: When using a cloud provider, there's no guarantee that the connection pool and database instance are running on the same host but there's always going to be a network hop. @@ -198,6 +202,7 @@ This is the core advantage of Prisma Postgres compared to other providers that a In this article, we looked under the covers of Prisma Postgres and the next-generation technology stack it's built on. If you are already using Prisma ORM, give Prisma Postgres a try [by importing the data from your existing database](https://www.prisma.io/docs/getting-started/prisma-postgres/import-from-existing-database) in . Otherwise, try out Prisma Postgres from scratch by running this command in your terminal: + ``` npx prisma@latest init --db -``` \ No newline at end of file +``` diff --git a/apps/blog/content/blog/cockroach-ga-5JrD9XVWQDYL/index.mdx b/apps/blog/content/blog/cockroach-ga-5JrD9XVWQDYL/index.mdx index 9b07080e30..8829160392 100644 --- a/apps/blog/content/blog/cockroach-ga-5JrD9XVWQDYL/index.mdx +++ b/apps/blog/content/blog/cockroach-ga-5JrD9XVWQDYL/index.mdx @@ -16,8 +16,8 @@ tags: --- On May 10th, we were thrilled to release version [3.14.0](https://github.com/prisma/prisma/releases/tag/3.14.0) of - Prisma ORM, which brought [CockroachDB](https://www.cockroachlabs.com/product/) support to GA! This production-ready - feature allows developers to make use of a scalable and resilient database. +Prisma ORM, which brought [CockroachDB](https://www.cockroachlabs.com/product/) support to GA! This production-ready +feature allows developers to make use of a scalable and resilient database. ## CockroachDB support in Prisma is now Generally Available 💙 @@ -66,11 +66,13 @@ model User { + age Int } ``` + Then create a new migration to account for that change. ```shell npx prisma migrate dev --name add-age ``` + ![](/cockroach-ga-5JrD9XVWQDYL/imgs/migration.png) Finally, ideally during a CI/CD step, the changes can be deployed to the database and CockroachDB will apply these across all of the databases in the cluster without downtime. @@ -78,6 +80,7 @@ Finally, ideally during a CI/CD step, the changes can be deployed to the databas ```shell npx prisma migrate deploy ``` + ## Effectively optimize your queries On top of the performance and scaling benefits of a distributed serverless database, Prisma allows developers to fine-tune their database to fit the querying needs of their applications. @@ -97,7 +100,8 @@ To jump in and begin building with CockroachDB and Prisma, you can use [Prisma M To get started with CockroachDB and Prisma, you can follow our guide to set up a new project from scratch.
-[Start from scratch with CockroachDB](https://www.prisma.io/docs/getting-started/setup-prisma/start-from-scratch/relational-databases/connect-your-database-typescript-cockroachdb) +[Start from scratch with +CockroachDB](https://www.prisma.io/docs/getting-started/setup-prisma/start-from-scratch/relational-databases/connect-your-database-typescript-cockroachdb) ### ... or use Prisma with your existing CockroachDB database @@ -106,4 +110,5 @@ If you already have an existing project that uses a CockroachDB database, you ca Prisma's _introspection_ features reads the schema of your database and automatically builds the Prisma schema with those models.
-[Add Prisma to existing CockroachDB project](https://www.prisma.io/docs/getting-started/setup-prisma/add-to-existing-project/relational-databases-typescript-cockroachdb) +[Add Prisma to existing CockroachDB +project](https://www.prisma.io/docs/getting-started/setup-prisma/add-to-existing-project/relational-databases-typescript-cockroachdb) diff --git a/apps/blog/content/blog/compliance-reqs-complete/index.mdx b/apps/blog/content/blog/compliance-reqs-complete/index.mdx index de58286d91..9b527df18c 100644 --- a/apps/blog/content/blog/compliance-reqs-complete/index.mdx +++ b/apps/blog/content/blog/compliance-reqs-complete/index.mdx @@ -34,7 +34,7 @@ The General Data Protection Regulation (GDPR) is a comprehensive data protection ### ISO 27001 -ISO 27001 is an international standard for information security management systems (ISMS). By completing all the required steps for this comprehensive standard, our customers are ensured that Prisma has implemented a systematic approach to managing sensitive company and customer information. This includes risk management, ensuring data integrity, and protecting against unauthorized access. +ISO 27001 is an international standard for information security management systems (ISMS). By completing all the required steps for this comprehensive standard, our customers are ensured that Prisma has implemented a systematic approach to managing sensitive company and customer information. This includes risk management, ensuring data integrity, and protecting against unauthorized access. ### The Value of Compliance for Our Customers @@ -46,6 +46,6 @@ ISO 27001 is an international standard for information security management syste ### Commitment to Ongoing Compliance -Those who understand the compliance process and have gone through it know that this not a one-time effort but a continuous process of improvement. We are committed to regularly reviewing and enhancing our security measures to stay ahead of potential threats and comply with evolving regulations. Our dedication to completing and maintaining the requirements for SOC2 Type II, HIPAA, GDPR, and ISO 27001:2022 certifications reflects our promise to provide secure, reliable, and trustworthy solutions for our customers. +Those who understand the compliance process and have gone through it know that this not a one-time effort but a continuous process of improvement. We are committed to regularly reviewing and enhancing our security measures to stay ahead of potential threats and comply with evolving regulations. Our dedication to completing and maintaining the requirements for SOC2 Type II, HIPAA, GDPR, and ISO 27001:2022 certifications reflects our promise to provide secure, reliable, and trustworthy solutions for our customers. For more information about our compliance journey and how Prisma can help you achieve your data security goals, feel free to visit our [Trust Center](https://trust.prisma.io/) or contact us at [compliance@prisma.io](mailto:compliance@prisma.io). diff --git a/apps/blog/content/blog/connect-your-apps-to-prisma-postgres-via-vercel-marketplace-integration/index.mdx b/apps/blog/content/blog/connect-your-apps-to-prisma-postgres-via-vercel-marketplace-integration/index.mdx index 9837651d94..8b4bb11e33 100644 --- a/apps/blog/content/blog/connect-your-apps-to-prisma-postgres-via-vercel-marketplace-integration/index.mdx +++ b/apps/blog/content/blog/connect-your-apps-to-prisma-postgres-via-vercel-marketplace-integration/index.mdx @@ -34,7 +34,7 @@ With the new Prisma Postgres integration, you can: ## Deploy our Next.js starter template now -To get you started, we have built a [starter template](https://vercel.com/templates/next.js/prisma-postgres) for you that can deploy with a single-click. +To get you started, we have built a [starter template](https://vercel.com/templates/next.js/prisma-postgres) for you that can deploy with a single-click. ![](/connect-your-apps-to-prisma-postgres-via-vercel-marketplace-integration/imgs/37841e4d757e5ec1c09e6285933fc7ec16ad1902-3248x2112.png) diff --git a/apps/blog/content/blog/connections-edges-nodes-in-relay-758d358aa4c7/index.mdx b/apps/blog/content/blog/connections-edges-nodes-in-relay-758d358aa4c7/index.mdx index 125dba10c4..ce7469d137 100644 --- a/apps/blog/content/blog/connections-edges-nodes-in-relay-758d358aa4c7/index.mdx +++ b/apps/blog/content/blog/connections-edges-nodes-in-relay-758d358aa4c7/index.mdx @@ -11,7 +11,7 @@ heroImagePath: "/connections-edges-nodes-in-relay-758d358aa4c7/imgs/hero-a86c8de --- The terminology of Relay can be quite overwhelming in the beginning. Relay introduces a handful of new concepts on top - of GraphQL, mainly in order to manage relationships between models.. +of GraphQL, mainly in order to manage relationships between models.. This already leads to the first new term: a one-to-many relationship between two models is called a **connection**. @@ -29,6 +29,7 @@ Let’s consider this following simple GraphQL query. It fetches the `releaseDat } } ``` + Now let’s take this query and adjust it to the expected format of Relay. ```graphql @@ -44,6 +45,7 @@ Now let’s take this query and adjust it to the expected format of Relay. } } ``` + ## Edges and nodes Okay, let’s see what’s going on here. The `actors` connection now has a more complex structure containing the fields `edges` and `node`. These terms should be a bit more clear when looking at the following image. @@ -57,4 +59,3 @@ Lastly, we also notice the first: 10 parameter on the actors field. This gives u ## Further reading This was just a brief overview on connections in Relay. If you want to dive deeper please check out the [Relay docs on connections](https://relay.dev/docs/api-reference/store/#connectionhandler) or explore the [Relay Cursor Connections Specification](https://relay.dev/graphql/connections.htm). - diff --git a/apps/blog/content/blog/convergence/index.mdx b/apps/blog/content/blog/convergence/index.mdx index 7a27f2dadb..fc99f69695 100644 --- a/apps/blog/content/blog/convergence/index.mdx +++ b/apps/blog/content/blog/convergence/index.mdx @@ -16,16 +16,18 @@ Drizzle's relational API v2 adds migrations, object-based queries, and abstracti A few years ago, Drizzle ORM burst onto the scene with a compelling pitch: at "~7.4kb minified+gzipped," they positioned themselves as the anti-Prisma. No heavy Rust engine, no migration complexity, no abstractions hiding SQL. Just lightweight, typesafe SQL that stays close to the metal. And honestly they had a point. The "just write SQL" philosophy is genuinely appealing, especially to developers who know databases well. We've been called "bloated," "over-engineered," and "too magical" enough times to appreciate why a fresh, minimal approach resonated with developers. Fast forward to 2025. Drizzle's "Relational API v2" and their massive "Alternation Engine" [PR #4439](https://github.com/drizzle-team/drizzle-orm/pull/4439) tell a different story. As of this writing, this new branch has 363 commits, [9,000+ tests](https://github.com/drizzle-team/drizzle-orm/issues/2061#issuecomment-3239454117), a systematic implementation of migrations, object-based queries, and abstractions. As someone who's been through that exact journey (we remember when our migration test suite exploded), we can say with authority: this is impressive, necessary engineering work. -Here's what makes this really cool: two ORMs with opposite starting philosophies, built by different teams, are converging on remarkably similar thinking and arch. This isn't about who copied whom, it's about discovering what production applications actually need, and then doing the necessary to serve your the needs of your users. It's also about what this convergence reveals: certain patterns aren't arbitrary preferences made by engineering teams or biased framework opinions, they're optimal solutions that emerge when you tackle production-scale problems in complicated domains like databases. +Here's what makes this really cool: two ORMs with opposite starting philosophies, built by different teams, are converging on remarkably similar thinking and arch. This isn't about who copied whom, it's about discovering what production applications actually need, and then doing the necessary to serve your the needs of your users. It's also about what this convergence reveals: certain patterns aren't arbitrary preferences made by engineering teams or biased framework opinions, they're optimal solutions that emerge when you tackle production-scale problems in complicated domains like databases. Whether you start lightweight or comprehensive, you eventually end up in a similar place and we think that's pretty cool. And we'd be lying if we said it doesn't feel validating. 😉 ## The Migration Engine: Complexity Demands Specialized Attention + Let's talk about migrations. Drizzle has always offered schema-first migrations and automated tooling, and their initialk commands for the same have been remarkably similar to Prisma's from day one. But their ongoing Alternation Engine work now reveals something we learned the hard way back in 2021: building reliable migrations across databases isn't a weekend project, rather it's a sustained, core engineering effort. The test suite expansion alone tells the unavoidble story. You don't go from 600 to over 9,000 tests because you're over-engineering. You do it because migrations are genuinely complex: handling edge cases across database vendors, tricky alterations (column renames, type changes), data preservation during schema evolution, and the intricacies of multi-dialect support safely. ### Migration Commands: Parallel Evolution Validates Design + ```shell # Prisma's approach (since 2021) npx prisma db push # Push schema state to database @@ -39,17 +41,20 @@ npx drizzle-kit pull # Pull database state to schema (introspection) npx drizzle-kit migrate # Apply migrations npx drizzle-kit generate # Generate migrations ``` + The reason that these command structures have been remarkably similar from the start is simple: when you're building production-grade migration tooling that needs introspection, code generation, schema diffing, and migration execution across multiple database vendors, you will naturally arrive at similar command structures. Both teams independently discovered that separating rapid prototyping (push/pull) from stable production workflows (generate/migrate) isn't an arbitrary choice; it's a necessary operational pattern. This parallel evolution proves that these core design patterns were never over-engineering. They are simply what production migration systems mandate. We learned this through years of difficult iteration. Drizzle is learning it now through their comprehensive rewrite. It’s the same lessons, just on a different timeline. ### What The Rewrite Reveals + ```shell PR #4439 - "Alternation Engine" - 363 commits (to date) -- ~167k lines added, 67k removed +- ~167k lines added, 67k removed - Test suite: 600 → 9k+ tests ``` + Here's what we truly respect about the Alternation Engine work: it shows a team that's willing to execute a massive rebuild when users need more. The scope of this endeavor, refining diffing, restructuring folders, adding CockroachDB and SQL Server support, is the exact gritty technical road we traveled with Prisma Migrate. It's not glamorous work, but it's undeniably essential. The commitment to 9,000 tests isn't just about completeness. They’re irrefutable proof that getting migrations right across different databases is genuinely difficult. You can't hand-wave edge cases. You simply cannot ignore the intricacies of ALTER TABLE across vendors. Production usage exposes every single corner case. @@ -59,16 +64,18 @@ To be clear: Drizzle's SQL-first philosophy has genuine, specific merit. For tea But when you scale to production complexity, need to coordinate schema changes across multiple teams, maintaining reliability across environments, and moving fast without breaking things, you inevitably end up building what they're building now: sophisticated diffing, extensive testing, and robust tooling. The [9k tests](https://github.com/drizzle-team/drizzle-orm/issues/2061#issuecomment-3239454117) aren't overkill. They are the mandated cost of doing migrations right at scale. ## Relational Queries: The Complete Convergence + Okay, this is where it gets technically interesting. Let's talk about relational queries. In their own [Relational API v2 discussion](https://github.com/drizzle-team/drizzle-orm/discussions/2316), Drizzle's team made an interesting admission: they acknowledged that their original approach had "several major flaws" and concluded that object-based filters, the exact pattern their early positioning dismissed as unnecessary abstraction, now "just feels natural." -We don't say this to gloat or score cheap points. We say it because watching another team arrive at the same conclusion through independent experience is genuinely validating. It confirms that this design choice is simply superior for developer experience at scale. +We don't say this to gloat or score cheap points. We say it because watching another team arrive at the same conclusion through independent experience is genuinely validating. It confirms that this design choice is simply superior for developer experience at scale. Let's look at the APIs side by side. ### Fetching Related Data - Spot the Difference + ```tsx // Let's look at a real-world query: fetching published -// posts with author details and recent comments. +// posts with author details and recent comments. // The similarity is remarkable: // Prisma (since 2020) @@ -78,18 +85,18 @@ const postsWithDetails = await prisma.post.findMany({ author: { select: { name: true, - email: true - } + email: true, + }, }, comments: { where: { approved: true }, include: { - user: true + user: true, }, - orderBy: { createdAt: 'desc' }, - take: 5 - } - } + orderBy: { createdAt: "desc" }, + take: 5, + }, + }, }); // Drizzle's Relational Queries v2 (2025) @@ -99,20 +106,21 @@ const postsWithDetails = await db.query.posts.findMany({ author: { columns: { name: true, - email: true - } + email: true, + }, }, comments: { where: { approved: true }, with: { - user: true + user: true, }, - orderBy: { createdAt: 'desc' }, - limit: 5 - } - } + orderBy: { createdAt: "desc" }, + limit: 5, + }, + }, }); ``` + The APIs are nearly identical: | Prisma | Drizzle Relational API v2 | | --- | --- | @@ -126,8 +134,8 @@ When we launched this API in 2020, people loudly said we were over-abstracting S Here's the undeniable thing folks: we're not claiming they copied us (though we'd be flattered). We're claiming that when you build for production-scale relational queries, you mujst naturally converge on these patterns. They feel natural because they are fundamentally superior for developer experience and correctnes. - The unavoidable takeaway comes stratight from Drizzle's [v2 migration documentation](https://rqbv2.drizzle-orm-fe.pages.dev/docs/relations-v1-v2), which lists the features conspicuously "not supported in v1": + - Filtering by relations - Using `offset` on nested relations - Predefined filters in relation definitions @@ -139,9 +147,10 @@ Every single one of these items has been a core, fundamental Prisma feature sinc Another final piece of evidence is structural: Drizzle RQB v2 deprecated their callback-based syntax entirely, moving it to `db._query` and `import from 'drizzle-orm/_relations'` while giving the clean `db.query` namespace to object-based patterns. That underscore prefix is a universally understood signal JavaScript, conventionally marking code as "internal," "deprecated," or "legacy." Their [v2 migration docs](https://rqbv2.drizzle-orm-fe.pages.dev/docs/relations-v1-v2) confirm: "`where` is now object" and "`orderBy` is now object." This isn't incremental improvement. It's a philosophical pivot driven by what users actually need. The new default is object-based queries…the exact pattern Prisma pioneered. Consider this example: + ```sql -- Raw SQL equivalent: published posts with author + latest 5 approved comments -SELECT +SELECT p.id, p.title, p.content, u.name as author_name, u.email as author_email, COALESCE(c.comment_data, '[]'::json) AS comment_data @@ -159,33 +168,36 @@ LEFT JOIN LATERAL ( ) ORDER BY c.created_at DESC ) as comment_data FROM ( - SELECT * FROM comments - WHERE post_id = p.id AND approved = true - ORDER BY created_at DESC + SELECT * FROM comments + WHERE post_id = p.id AND approved = true + ORDER BY created_at DESC LIMIT 5 ) c INNER JOIN users cu ON c.user_id = cu.id ) c ON true WHERE p.published = true; ``` + ```tsx // Prisma: Same query, readable and maintainable const posts = await prisma.post.findMany({ where: { published: true }, include: { author: { - select: { name: true, email: true } + select: { name: true, email: true }, }, comments: { where: { approved: true }, include: { user: true }, - orderBy: { createdAt: 'desc' }, - take: 5 - } - } + orderBy: { createdAt: "desc" }, + take: 5, + }, + }, }); ``` + The readability difference is stark, and it's about maintenance overhead. The SQL demands LATERAL joins, JSON aggregation, deeply nested subqueries, and incredibly careful correlation. One wrong alias or forgotten JOIN condition and you're debugging for hours. The object-based version reads like a description of what you want. For simple queries? Sure, raw SQL is fine. But production applications need code you can reliably reason about at 3am, debug without a PhD in SQL optimization, and onboard junior developers to understand quickly. This operational necessity is precisely why both ORMs converged here. The "close to SQL" approach works great for straightforward queries and individual projects. But production-level complexity with distributed teams mandates higher-level patterns. Drizzle's users have perhaps learned this the hard way and are demanding a change. The Drizzle team listened and made the right call. That's good product development. + ```tsx *// Drizzle v1 - Manually navigate junction tables* const usersWithGroups = await db.query.users.findMany({ @@ -205,17 +217,21 @@ const mapped = usersWithGroups.map(user => ({ groups: user.usersToGroups.map(utg => utg.group) })); ``` + Having to manually navigate junction tables and map results yourself isn't "close to SQL," it's a maintenance nightmare. Drizzle RQB v2 finally added first-class many-to-many support, providing the seamless DX Prisma has always provided. Again, this isn't criticism: it's validation. The "just write SQL" approach always needs abstraction layers when real users build real applications that feature complex relationships and when team members have different skill levels. We learned this essential lesson in 2019. Drizzle learned it through direct user feedback. Same lesson, different cost. ## Performance: Everyone's Getting Better + Here's where the story gets truly interesting, and honestly, kind of fun. While Drizzle is forced to add sophisticated features (migrations, relational queries, growing test coverage), Prisma went the opposite direction in terms of architecture: we [removed our entire Rust engine](https://www.prisma.io/blog/rust-free-prisma-orm-is-ready-for-production), becoming 90% smaller and 3-4x faster in many scenarios. -Wait, smaller *and* more features? Yes. Turns out the complexity isn't in the feature set, it's in the underlying architecture. We spent years building sophisticated query engines and migration tooling in Rust, only to discover we could achieve better results by optimizing our TypeScript implementation. TypeScript is making quite the waves when it comes to adoption, so like dutiful devs, we observe and adapt. Sometimes you need to build the complex version first specifically to learn what complexity you actually need to discard. Such is the way of the *Force*. +Wait, smaller _and_ more features? Yes. Turns out the complexity isn't in the feature set, it's in the underlying architecture. We spent years building sophisticated query engines and migration tooling in Rust, only to discover we could achieve better results by optimizing our TypeScript implementation. TypeScript is making quite the waves when it comes to adoption, so like dutiful devs, we observe and adapt. Sometimes you need to build the complex version first specifically to learn what complexity you actually need to discard. Such is the way of the _Force_. ### Type-Checking Performance + There's another crucial dimension worth discussing: DX during actual coding. Our [comprehensive benchmarks](https://www.prisma.io/blog/why-prisma-orm-checks-types-faster-than-drizzle) with TypeScript expert David Blass already revealed something interesting about performance and Type overhead. But even more telling, Drizzle's own community raised concerns in their [v2 discussion](https://github.com/drizzle-team/drizzle-orm/discussions/2316), noting that "Prisma has a codegen step to create the types which would help the approach scale." That single comment cuts to the heart of the matter. While Drizzle relies on complex runtime inference from procedural TypeScript, Prisma uses a dedicated, declared schema ([PSL](https://www.prisma.io/blog/prisma-schema-language-the-best-way-to-define-your-data)) to generate a perfectly type-safe client. This code generation step, which Drizzle's early philosophy dismissed as "heavy," is the mandatory gateway to superior IntelliSense, predictable type safety, and efficient development when your schema reaches any non-trivial size. + ```shell // Type instantiations (lower is better) Schema Types: @@ -227,21 +243,24 @@ Schema Types: Queries: Prisma is ~1.5x faster than Drizzle Beta Prisma is ~2.1x faster than Drizzle Stable ``` + Why? Prisma generates types at build time while Drizzle infers them on every keystroke: + ```tsx // Prisma: Types precomputed during `prisma generate` // Result: .d.ts files with optimized types type User = { - id: number - email: string - name: string | null - posts: Post[] -} + id: number; + email: string; + name: string | null; + posts: Post[]; +}; // Drizzle: Types inferred from schema on every compile // Result: TypeScript does heavy lifting repeatedly -type User = InferSelectModel // Complex inference chain +type User = InferSelectModel; // Complex inference chain ``` + The point isn't "we win, they lose." The point is that different architectural choices have real tradeoffs. Code generation adds a build step but delivers [faster type-checking](https://benchmarks.prisma.io/?tab=type). Type inference is more immediate but can get expensive at scale. There's no free lunch, just different costs. ## What We're All Learning @@ -266,6 +285,7 @@ Step back and look at the unfolding pattern: two ORMs with opposite starting phi These patterns aren't "Prisma patterns," they're solutions that emerge when solving production database problems. We got here first. We'd argue our path was more direct. But the observed convergence validates tthe destination. ## Meanwhile, at Prisma + While the ecosystem converges on similar ORM patterns, we've been pushing in a different direction entirely. The Rust-to-TypeScript migration shows our commitment to DX over dogma. We built sophisticated Rust engines, learned what complexity we actually needed, then rebuilt in a way that's smaller and faster. That's what [Prisma v7](https://www.prisma.io/blog/announcing-prisma-orm-7-0-0) is all about, and we're proud of it. @@ -279,21 +299,25 @@ generator client { engineType = "client" } ``` + ```tsx // Step 2: Install adapter npm install @prisma/adapter-pg pg ``` + ```tsx // Step 3: Use it -import { PrismaClient } from '@prisma/client' -import { PrismaPg } from '@prisma/adapter-pg' +import { PrismaClient } from "@prisma/client"; +import { PrismaPg } from "@prisma/adapter-pg"; -const adapter = new PrismaPg(process.env.DATABASE_URL!) -const prisma = new PrismaClient({ adapter }) +const adapter = new PrismaPg(process.env.DATABASE_URL!); +const prisma = new PrismaClient({ adapter }); ``` + This is the kind of innovation that happens when you're not busy rebuilding features you once claimed weren't needed (ok, we admit, that was a little spicy). But seriously, we're excited about where this is going.) And here's the thing: we genuinely want other ORMs work great with Prisma Postgres too. A rising tide lifts all boats. More good ORMs means more developers building with databases, which means more potential Prisma Postgres users. Everyone wins. ## Looking Forward: Why Convergence Matters + The evolution from Drizzle v1 to v2 isn't just feature additions, it's a fundamental philosophical pivot validated through production experience. They deprecated callback-based queries, moved them to underscore-prefixed APIs, and gave the clean namespace to Prisma-like patterns. Their docs repeatedly note features that were "not supported in v1" but are now core to v2. We're flattered. We're validated. And honestly? We're impressed. Building a comprehensive relational query system and migration engine is genuinely hard work. The Drizzle team is doing it well. diff --git a/apps/blog/content/blog/coo-announcement-aer1fgviirjb/index.mdx b/apps/blog/content/blog/coo-announcement-aer1fgviirjb/index.mdx index ca5fb732c3..e24a070403 100644 --- a/apps/blog/content/blog/coo-announcement-aer1fgviirjb/index.mdx +++ b/apps/blog/content/blog/coo-announcement-aer1fgviirjb/index.mdx @@ -19,7 +19,6 @@ We’re excited to announce that [Nitin Gupta](https://linkedin.com/in/gniting) As COO, Nitin will oversee our commercial, people, and operational functions, focusing on alignment and optimization to support the delivery of our product ambitions. This is a pivotal role for Prisma as we continue to build upon the upward trajectory of our product adoption in the market. -“*Since our [last fundraise](https://www.prisma.io/blog/series-b-announcement-v8t12ksi6x), we’ve felt the need to augment our executive team, and Nitin brings the right mix of experience, foresight, and execution rigor. I’m excited to have him join Prisma and be my sparring partner.*” Søren Bramer Schmidt, CEO @ Prisma. +“_Since our [last fundraise](https://www.prisma.io/blog/series-b-announcement-v8t12ksi6x), we’ve felt the need to augment our executive team, and Nitin brings the right mix of experience, foresight, and execution rigor. I’m excited to have him join Prisma and be my sparring partner._” Søren Bramer Schmidt, CEO @ Prisma. We are thrilled to have Nitin on board and look forward to the contributions he will make as Prisma continues to grow and evolve. Welcome to the team, Nitin! - diff --git a/apps/blog/content/blog/data-platform-static-ips/index.mdx b/apps/blog/content/blog/data-platform-static-ips/index.mdx index 94cc6e297a..c60551e423 100644 --- a/apps/blog/content/blog/data-platform-static-ips/index.mdx +++ b/apps/blog/content/blog/data-platform-static-ips/index.mdx @@ -15,11 +15,11 @@ tags: --- We're launching Early Access support for static egress IPs. Keep your databases secure by ensuring that the Prisma - Data Platform only connects to your database from specific IPs. [Try it out](https://cloud.prisma.io/) and share your - feedback. +Data Platform only connects to your database from specific IPs. [Try it out](https://cloud.prisma.io/) and share your +feedback. > ⚠️ **Outdated Information** -> +> > Please be aware that the information provided in this blog post is outdated. Since its publication, there have been updates to the Prisma Data Platform, including the discontinuation of the Data Proxy. > If you require connection pooling and global caching, we recommend exploring [Prisma Accelerate](https://www.prisma.io/data-platform/accelerate). > For the latest details on our platform's products and features, please visit our [website](https://www.prisma.io/) and consult our [changelog](https://www.prisma.io/changelog). @@ -66,7 +66,6 @@ You can enable static egress IPs for new and existing projects can use their dat > **Note:** IP addresses are specific to the region where the data proxy is configured. Changing the region of the Data Proxy will change the IPs for egress and will thus require a change of the IP allow list on your provider - ### Enabling static egress IPs You can enable static egress IPs per environment in both new and existing projects. @@ -102,7 +101,7 @@ To get a glimpse into our current priorities and upcoming features, check out ou ## Try the static egress IPs and share your feedback > ⚠️ **Outdated Information** -> +> > Please be aware that the information provided in this blog post is outdated. Since its publication, there have been updates to the Prisma Data Platform, including the discontinuation of Prisma Data Proxy. If you require connection pooling and global caching, we recommend exploring [**Prisma Accelerate**](https://www.prisma.io/data-platform/accelerate). For the latest details on our platform's products and features, please visit our [**website**](https://www.prisma.io/) and consult our [**changelog**](https://www.prisma.io/changelog). Since the [static egress IPs](https://cloud.prisma.io/login) is in [Early Access](https://www.prisma.io/docs/data-platform/about/releases#early-access), we don't recommend using it in production. diff --git a/apps/blog/content/blog/database-access-in-react-server-components-r2xgk9aztgdf/index.mdx b/apps/blog/content/blog/database-access-in-react-server-components-r2xgk9aztgdf/index.mdx index c97d10d03e..58cbd932bf 100644 --- a/apps/blog/content/blog/database-access-in-react-server-components-r2xgk9aztgdf/index.mdx +++ b/apps/blog/content/blog/database-access-in-react-server-components-r2xgk9aztgdf/index.mdx @@ -93,24 +93,26 @@ function ArtistPage({ artistId }) { - ) + ); } ``` + ### Building a fast and consistent user experience To add data fetching logic to an API, we'd need to fetch all data at once and pass it down to the different components. This way, we can achieve a consistent user experience by rendering all components at once. So we would end up with something like this: ```jsx function ArtistPage({ artistId }) { - const data = fetchAllData() + const data = fetchAllData(); return ( - ) + ); } ``` + This approach is fast because we only need to make a single request to our API. However, we find that the code is now **harder to maintain**. The reason being that the UI components are directly tightly coupled to the API response. So if we make a change in our UI, we need to update the API accordingly and vice-versa. @@ -150,6 +152,7 @@ function ArtistPage ({ artistId }){ ) } ``` + This approach is not fast because our parent component's children only start fetching data *after* the parent makes a request, receives a response, and renders. So we end up having a waterfall of network requests, where network requests start one after the other, instead of all at once: @@ -165,20 +168,20 @@ So in our Spotify app example, this is what our components will look like: function ArtistPage({ artistId }) { // requests will not finish at the same time // nor at the same order - const details = fetchDetails(artistId).data - const topTracks = fetchTopTracks(artistId).data - const discography = fetchDiscography(artistId).data + const details = fetchDetails(artistId).data; + const topTracks = fetchTopTracks(artistId).data; + const discography = fetchDiscography(artistId).data; return ( - ) + ); } ``` -This pattern will result in inconsistent behavior because if all components start fetching data together, they don't necessarily finish simultaneously. That's because the data fetching process depends on the network connection, which can vary. So while now we have fast, easy-to-maintain code, we are sacrificing user experience. +This pattern will result in inconsistent behavior because if all components start fetching data together, they don't necessarily finish simultaneously. That's because the data fetching process depends on the network connection, which can vary. So while now we have fast, easy-to-maintain code, we are sacrificing user experience. So is it impossible to have all three? Not really. @@ -231,6 +234,7 @@ cd react-server-components-demo npm install npm start ``` + The app will be running at [http://localhost:4000](http://localhost:4000) and this is what you'll see: ![Server Components demo screenshot](/database-access-in-react-server-components-r2xgk9aztgdf/imgs/server-components-demo.png) @@ -273,6 +277,7 @@ server-components-demo/ ┣ db.server.js ┗ index.client.js ``` + The `/notes` directory is where we save notes, in markdown format, when they're created on the frontend. The `/prisma` directory contains two files: @@ -298,6 +303,7 @@ model Note { body String? } ``` + The schema file is written in Prisma Schema Language (PSL). To get the best possible development experience, make sure you install our [VSCode extension,](https://marketplace.visualstudio.com/items?itemName=Prisma.prisma) which adds syntax highlighting, formatting, auto-completion, jump-to-definition, and linting for `.prisma` files. We specified that we're using SQLite and our `dev.db` file location in the `datasource` field. @@ -338,34 +344,36 @@ To create a note we created a `/notes` endpoint that handles `POST` requests. In ```js app.post( - '/notes', - handleErrors(async function(req, res) { + "/notes", + handleErrors(async function (req, res) { const result = await prisma.note.create({ data: { body: req.body.body, title: req.body.title, }, - }) + }); // ... // return newly created note's id // in the response object - sendResponse(req, res, result.id) + sendResponse(req, res, result.id); }), -) +); ``` + To get all notes, we created a `/notes` route and when we receive a `GET` request we will call and await the `findMany()` function to return all records inside the `notes` table in our database. ```js app.get( - '/notes', - handleErrors(async function(_req, res) { + "/notes", + handleErrors(async function (_req, res) { // return all records - const notes = await prisma.note.findMany() - res.json(notes) + const notes = await prisma.note.findMany(); + res.json(notes); }), -) +); ``` + A `GET` request to `/note/id` will return a single note when we pass its `id`. We get the note's `id` from the request's parameters using `req.param.id` and cast it to a number, since that's the type of the `id` we defined in our Prisma schema. @@ -374,24 +382,25 @@ We then use `findUnique` which returns a single record by a unique identifier. ```js app.get( - '/notes/:id', - handleErrors(async function(req, res) { + "/notes/:id", + handleErrors(async function (req, res) { const note = await prisma.note.findUnique({ where: { id: Number(req.params.id), }, - }) - res.json(note) + }); + res.json(note); }), -) +); ``` + Finally, to update a note, we can send a `PUT` requests to `/notes/:id` and we access the note's id from the request parameters. We then pass it to the `update()` function and pass the note's updates coming from the request's body. ```js app.put( - '/notes/:id', - handleErrors(async function(req, res) { - const updatedId = Number(req.params.id) + "/notes/:id", + handleErrors(async function (req, res) { + const updatedId = Number(req.params.id); await prisma.note.update({ where: { id: updatedId, @@ -400,28 +409,30 @@ app.put( title: req.body.title, body: req.body.body, }, - }) + }); // ... - sendResponse(req, res, null) + sendResponse(req, res, null); }), -) +); ``` + To delete a note, we send a `DELETE` request to `/notes/:id`. We then pass the note's id from the request parameters to the `delete` function. ```js app.delete( - '/notes/:id', - handleErrors(async function(req, res) { + "/notes/:id", + handleErrors(async function (req, res) { await prisma.note.delete({ where: { id: Number(req.params.id), }, - }) + }); // ... - sendResponse(req, res, null) + sendResponse(req, res, null); }), -) +); ``` + Note that all Prisma Client operations are promise-based, that's why we need to use async/await (or promises) when sending database queries using Prisma Client. ### A look at Server Components @@ -438,10 +449,11 @@ So in the `db.server.js` file, we're creating a new instance of Prisma Client. H ```js //db.server.js -import { PrismaClient } from 'react-prisma' +import { PrismaClient } from "react-prisma"; -export const prisma = new PrismaClient() +export const prisma = new PrismaClient(); ``` + In the `NoteList.server.js` component, we're importing `prisma` and the `SidebarNote` component, which is a regular React component that receives a note object as a prop. We're filtering the list of notes by making a query to the database using Prisma. @@ -450,8 +462,8 @@ We're retrieving all records inside the `notes` table, where the `title` of a no ```jsx // NoteList.server.js -import { prisma } from './db.server' -import SidebarNote from './SidebarNote' +import { prisma } from "./db.server"; +import SidebarNote from "./SidebarNote"; export default function NoteList({ searchText }) { const notes = prisma.note.findMany({ @@ -460,11 +472,11 @@ export default function NoteList({ searchText }) { contains: searchText ?? undefined, }, }, - }) + }); return notes.length > 0 ? (
    - {notes.map(note => ( + {notes.map((note) => (
  • @@ -472,11 +484,14 @@ export default function NoteList({ searchText }) {
) : (
- {searchText ? `Couldn't find any notes titled "${searchText}".` : 'No notes created yet!'}{' '} + {searchText + ? `Couldn't find any notes titled "${searchText}".` + : "No notes created yet!"}{" "}
- ) + ); } ``` + You'll notice that we don't need to `await` prisma here, that's because React uses a different mechanism that retries rendering when the data is cached. So it's still asynchronous, but you don't need to use async/await. ## Conclusion @@ -490,4 +505,3 @@ We also end up having a faster user experience since less JavaScript is shipped Finally, React's virtual DOM now spans the entire application instead of just the client. There are still many questions to be answered, and there are [drawbacks](https://github.com/josephsavona/rfcs/blob/server-components/text/0000-server-components.md#drawbacks), but it's exciting to see how the future of building Web apps using React might look like. - diff --git a/apps/blog/content/blog/database-access-on-the-edge-8F0t1s1BqOJE/index.mdx b/apps/blog/content/blog/database-access-on-the-edge-8F0t1s1BqOJE/index.mdx index c3c4bafd6b..171d94bb10 100644 --- a/apps/blog/content/blog/database-access-on-the-edge-8F0t1s1BqOJE/index.mdx +++ b/apps/blog/content/blog/database-access-on-the-edge-8F0t1s1BqOJE/index.mdx @@ -15,7 +15,6 @@ tags: The Edge enables application deployment across the globe. This article explores what Edge environments are, the challenges that arise when working in Edge environments and how to access databases on the Edge using Prisma Accelerate. - ## What is the Edge? Traditionally, applications would be deployed to a single region or data center, in either a virtual machine, Platform as a Service (PaaS) like Heroku, or Functions as a Service (FaaS) like AWS Lambda. While this deployment pattern worked fine, the problem this created was that a user located on the other side of the globe would experience slightly longer response times. @@ -30,7 +29,6 @@ We took this a step further and introduced Edge computing such as [Vercel's Edge Edge computing works similarly to serverless functions, without the cold starts because they have a smaller runtime. This is great because web apps would perform better, but it comes at a cost: A smaller runtime on the Edge means that you don't have the exact same capabilities as you would have in regular Node.js runtime used in serverless functions. - ### Edge functions can easily exhaust database connections Edge functions are stateless, meaning they lack persistent state between requests. This architecture clashes with the stateful nature of traditional relational databases, where each request requires a new database connection. @@ -61,7 +59,6 @@ Here's a video from Jeff Delaney, [Fireship](https://fireship.io/), on whether " - ## Demo: Database access on the Edge Let's now take a look how to access a database from Vercel's Edge functions using Prisma Accelerate. @@ -93,6 +90,7 @@ model Quote { ### Prerequisites To successfully follow along, you will need: + - Node.js - A cloud-hosted [PostgreSQL](https://postgresql.org/) database ([set up a free PostgreSQL database on Supabase](https://dev.to/prisma/set-up-a-free-postgresql-database-on-supabase-to-use-with-prisma-3pk6) or on [Neon](https://neon.tech/)) - A [GitHub](https://github.com/) account to host your application code @@ -117,8 +115,8 @@ The page and API Route are also configured to use Vercel’s [Edge Runtime](http ```ts export const config = { - runtime: 'experimental-edge', -} + runtime: "experimental-edge", +}; ``` ### Set up the database @@ -176,20 +174,19 @@ git push -u origin main Once you’ve set up your repository, navigate to the [Platform Console](https://console.prisma.io/) and sign up for a free account if you don’t have one yet. - After signing up: 1. Create a new project by clicking the **New project** button - ![](/database-access-on-the-edge-8F0t1s1BqOJE/imgs/create-project.png) + ![](/database-access-on-the-edge-8F0t1s1BqOJE/imgs/create-project.png) 2. Fill out your **Project’s name** and then click the **Create Project** button - ![](/database-access-on-the-edge-8F0t1s1BqOJE/imgs/name-your-project.png) + ![](/database-access-on-the-edge-8F0t1s1BqOJE/imgs/name-your-project.png) 3. Enable Accelerate by clicking the **Enable Accelerate** button - ![](/database-access-on-the-edge-8F0t1s1BqOJE/imgs/project-dashboard.png) + ![](/database-access-on-the-edge-8F0t1s1BqOJE/imgs/project-dashboard.png) 4. Add your database connection string to the **Database connection string** field and select a region close to your database from the **Region** drop-down - ![](/database-access-on-the-edge-8F0t1s1BqOJE/imgs/add-database-url.png) + ![](/database-access-on-the-edge-8F0t1s1BqOJE/imgs/add-database-url.png) 5. Generate an Accelerate connection string by clicking the **Generate API key** button @@ -207,7 +204,7 @@ MIGRATE_DATABASE_URL="postgresql://USER:PASSWORD@HOST:PORT/DATABASE" ``` > The `MIGRATE_DATABASE_URL` variable will be used to apply any pending migrations during the build process. -The `package.json` file uses the `vercel-build` hook script to run `prisma migrate deploy && next build` +> The `package.json` file uses the `vercel-build` hook script to run `prisma migrate deploy && next build` Then install the [Prisma Accelerate client extension](https://www.npmjs.com/package/@prisma/extension-accelerate): diff --git a/apps/blog/content/blog/database-vs-application-demystifying-join-strategies/index.mdx b/apps/blog/content/blog/database-vs-application-demystifying-join-strategies/index.mdx index e19ac48052..4379cfc6c4 100644 --- a/apps/blog/content/blog/database-vs-application-demystifying-join-strategies/index.mdx +++ b/apps/blog/content/blog/database-vs-application-demystifying-join-strategies/index.mdx @@ -50,23 +50,24 @@ As a developer, you're probably used to working with _nested_ objects, which loo } } ``` + In this example, the "object hierarchy" is as follows: `post` → `author` → `profile`. -This kind of nested structure is how data is represented in most programming languages that have the concept of an _object_. +This kind of nested structure is how data is represented in most programming languages that have the concept of an _object_. -However, if you've worked with a SQL database before, you're probably aware that related data is represented differently there, namely in a _flat_ (or [_normalized_](https://en.wikipedia.org/wiki/Database_normalization)) way. With that approach, relations between entities are represented via _foreign keys_ that specify _references_ across tables. +However, if you've worked with a SQL database before, you're probably aware that related data is represented differently there, namely in a *flat* (or [_normalized_](https://en.wikipedia.org/wiki/Database_normalization)) way. With that approach, relations between entities are represented via *foreign keys* that specify _references_ across tables. Here's a visual representation of the two approaches: ![](/database-vs-application-demystifying-join-strategies/imgs/47ca5a5f744cde64f8caaa359a2870df758d3c91-3200x1256.png) -This is a huge difference, not only in the way data is _physically_ laid out on disk and in memory, but also when it comes to the _mental model_ and to reasoning about the data. +This is a huge difference, not only in the way data is *physically* laid out on disk and in memory, but also when it comes to the *mental model* and to reasoning about the data. ### What does "joining" data mean? The process of joining data refers to getting the data from the _flat_ layout in a SQL database into a _nested_ structure that an application developer can use in their application. -This can happen in one of two places: +This can happen in one of two places: - In the **database**: A single SQL query is sent to the database. The query uses the `JOIN` keyword (or potentially a [correlated subquery](https://www.geeksforgeeks.org/sql-correlated-subqueries/)) to let the database perform the join across multiple tables and returns the nested structures. There are multiple ways of doing this join that we'll look at in the next section. - In the **application**: Multiple queries are sent to the database. Each query only accesses a single table and the query results are then joined in the application layer. @@ -75,9 +76,7 @@ Database-level joins have their benefits, but also some drawbacks if they're bec ## Three JOIN strategies: Naive, smart & application-level JOINs -At a high-level there are three different join strategies that can be applied, **"naive"** and **"smart"** JOINs on the DB-level, as well as **"application-level"** joins. Let's examine these one by one by use of the following schema: - - +At a high-level there are three different join strategies that can be applied, **"naive"** and **"smart"** JOINs on the DB-level, as well as **"application-level"** joins. Let's examine these one by one by use of the following schema: ```prisma model comments { @@ -105,6 +104,7 @@ model users { posts posts[] } ``` + ```sql CREATE TABLE users ( id SERIAL NOT NULL, @@ -132,8 +132,6 @@ CREATE INDEX idx_posts_author_id ON posts(author_id); CREATE INDEX idx_comments_post_id ON comments(post_id) ``` - - ### Naive DB-level JOINs lead to redundant data A naive DB-level JOIN refers to JOIN operations that don't take any additional measure for optimizations. These kinds of JOINs are often bad for performance for several reasons, let's explore! @@ -153,11 +151,12 @@ LEFT JOIN ORDER BY users.id, posts.id; ``` + The results returned by the database may look similar to this: ![](/database-vs-application-demystifying-join-strategies/imgs/aecb797ff9a4fec41786e1acfd8ea81be54ba073-404x571.png) -Do you notice something? There's _a lot_ of repetition in the data on the `user_name` column. +Do you notice something? There's _a lot_ of repetition in the data on the `user_name` column. Now, let's add the `comments` to the query: @@ -178,6 +177,7 @@ LEFT JOIN ORDER BY users.id, posts.id, comments.id ``` + Now that's even worse! Not only `user_name` repeats, but `post_title` does so as well: ![](/database-vs-application-demystifying-join-strategies/imgs/14d97834abcc4a48970178b9357c95b7c69773e1-1242x1694.png) @@ -186,8 +186,8 @@ The redundancy of the data has several negative implications: - Increased amount of (unnecessary) data that's sent over the wire, costing network bandwidth and increasing overall query latency. - The application layer needs to do additional work to arrive at the desired nested objects: - - deduplicate the redundant data - - re-construct the relationships between the data records + - deduplicate the redundant data + - re-construct the relationships between the data records Additionally, this kind of operation incurs a high CPU cost on the database, because it will query all three tables and perform its own in-memory mapping to join the data into one result set. @@ -203,9 +203,9 @@ In TypeScript, an example for this could look as follows (using a plain Postgres ```ts // Fetch data individually -const usersResult = await client.query('SELECT * FROM users'); -const postsResult = await client.query('SELECT * FROM posts'); -const commentsResult = await client.query('SELECT * FROM comments'); +const usersResult = await client.query("SELECT * FROM users"); +const postsResult = await client.query("SELECT * FROM posts"); +const commentsResult = await client.query("SELECT * FROM comments"); // Convert results to objects for easier processing const users = usersResult.rows; @@ -242,6 +242,7 @@ const joinedData = users.map((user) => { }; }); ``` + There are several benefits to this approach: - The database will generate a highly optimal execution plan for each of these queries and do virtually no CPU work since it's simply returning data from a single table. @@ -258,7 +259,7 @@ A major drawback, however, is that it requires multiple round trips to the datab Naive DB-level joins are almost never the best way to retrieve related data from your database, but does that mean your database should _never_ be responsible for joining data? Certainly not! -Database engines have become very powerful in the past years and constantly improved the ways how they optimize queries. In order to enable a database to generate the most optimal query plan, the most important thing is that it can understand the _intent_ of a query. +Database engines have become very powerful in the past years and constantly improved the ways how they optimize queries. In order to enable a database to generate the most optimal query plan, the most important thing is that it can understand the _intent_ of a query. There are two different factors to this: @@ -305,6 +306,7 @@ LEFT JOIN LATERAL ( GROUP BY u.id; ``` + Such a query produces the following results: ![](/database-vs-application-demystifying-join-strategies/imgs/ee53bcfbca9834771ff6856d9fcd6e77aef7bf04-1228x1708.png) @@ -318,11 +320,11 @@ While this query may yield better formatted results than the naive strategy, it ## The evolution of JOIN strategies in Prisma ORM -When Prisma ORM was [initially released in 2021](https://www.prisma.io/blog/prisma-the-complete-orm-inw24qjeawmb), it implemented the application-level join strategy for all its relation queries. +When Prisma ORM was [initially released in 2021](https://www.prisma.io/blog/prisma-the-complete-orm-inw24qjeawmb), it implemented the application-level join strategy for all its relation queries. -This strategy works really well when the application server and database are located closely to each other, helps with portability across database engines and increases scalability of the overall system (since application-layer CPU is easier and cheaper to scale than DB-level CPU). +This strategy works really well when the application server and database are located closely to each other, helps with portability across database engines and increases scalability of the overall system (since application-layer CPU is easier and cheaper to scale than DB-level CPU). -While the approach of application-level joins has served most developers well, it sometimes caused problems when application server and database couldn't be hosted closely to each other and the additional round trips negatively impacted overall query performance. +While the approach of application-level joins has served most developers well, it sometimes caused problems when application server and database couldn't be hosted closely to each other and the additional round trips negatively impacted overall query performance. That's why [we've added the smart DB-level joins as an alternative one year ago](https://www.prisma.io/blog/prisma-orm-now-lets-you-choose-the-best-join-strategy-preview), so developers have the option to always choose the most performant join strategy for their individual use case. @@ -332,9 +334,9 @@ Being able to use DB-level joins had been one of the [most popular feature reque ## Conclusion -Figuring out the most performant way to join data from multiple tables in a database is a complicated topic. In this article, we looked at three different approaches, _naive_ and _smart_ joins on the DB-level as well as _application-level_ joins. +Figuring out the most performant way to join data from multiple tables in a database is a complicated topic. In this article, we looked at three different approaches, _naive_ and _smart_ joins on the DB-level as well as _application-level_ joins. -Naive DB-level joins incur high CPU costs on the database server and lead to network overhead due to the unnecessary transfer of redundant data. +Naive DB-level joins incur high CPU costs on the database server and lead to network overhead due to the unnecessary transfer of redundant data. Application-level joins may be better suited for many scenarios due to their simplicity and cheap execution on the database-level. Systems using this strategy are also typically easier and less expensive to scale. diff --git a/apps/blog/content/blog/datadx-event-recap-z5pcp6hzbz5m/index.mdx b/apps/blog/content/blog/datadx-event-recap-z5pcp6hzbz5m/index.mdx index aeec6fe046..617762df86 100644 --- a/apps/blog/content/blog/datadx-event-recap-z5pcp6hzbz5m/index.mdx +++ b/apps/blog/content/blog/datadx-event-recap-z5pcp6hzbz5m/index.mdx @@ -15,11 +15,12 @@ tags: Explore the insights from the [Discover Data DX virtual event](https://www.datadx.io/event) held on December 7th, 2023. The event brought together industry leaders to discuss the significance and principles of the emerging Data DX category. -Software development is now accessible to a wider audience, yet the data complexity when building applications has grown. These shifts highlight the need for simpler and more intuitive tools that empower developers to focus on the applications they want to build. +Software development is now accessible to a wider audience, yet the data complexity when building applications has grown. These shifts highlight the need for simpler and more intuitive tools that empower developers to focus on the applications they want to build. The new [Data DX](https://www.datadx.io/) category embodies this vision to simplify data-driven application development without sacrificing depth or functionality. ### Bringing the industry together + Prisma's Discover Data DX event showcased the role of Data DX as a unifying concept. It was inspiring to see varied companies embrace Data DX's principles. Together, they explored its meaning and how collaborative efforts could enhance the developer experience significantly. - ## The evolution of database technologies + The first panel delved into how the developer experience with databases has evolved. Panelists from Prisma, Xata, PlanetScale, Snaplet, and Turso shared unique insights on enhancing the developer experience with databases. A central topic was distinguishing between necessary and accidental complexities in database management. The goal? **Simplify for developers** while recognizing some complexities are unavoidable. @@ -56,12 +57,14 @@ The panel also examined how to **balance powerful database capabilities and deve The discussion also covered emerging challenges in database management, such as schema changes, serverless technology, and AI/ML integration. -
+ +
[Watch the panel](https://www.youtube.com/watch?v=6_y0CUehlUA&t=385s) **Panelists** + - [Deepthi Sigireddi](https://twitter.com/ATechGirl), Engineering Lead for Vitess at **PlanetScale** -- [Glauber Costa](https://twitter.com/glcst), Founder/CEO at **Turso** +- [Glauber Costa](https://twitter.com/glcst), Founder/CEO at **Turso** - [Peter Pistorius](https://twitter.com/appfactory), Founder at **Snaplet** - [Tudor Golubenco](https://twitter.com/tudor_g), CTO at **Xata** - [Søren Bramer Schmidt](https://twitter.com/sorenbs), CEO at **Prisma** @@ -69,7 +72,8 @@ The discussion also covered emerging challenges in database management, such as Moderated by [Petra Donka](https://twitter.com/petradonka), Head of Developer Connections at **Prisma** ## The future of building data-driven apps -The second segment focused on the evolution of developer experience in data-driven application development, with representatives from Grafbase, Tinybird, RedwoodJS and Prisma sharing diverse perspectives. + +The second segment focused on the evolution of developer experience in data-driven application development, with representatives from Grafbase, Tinybird, RedwoodJS and Prisma sharing diverse perspectives. ### The crucial role of developer experience @@ -93,10 +97,12 @@ The consensus was that modern tools should significantly **reduce time to market The discussion highlighted the gap in tooling quality between front-end and back-end development, especially in data management. Beyond pure scalability, the industry is now shifting to prioritize ease of use and developer experience in back-end and data tools. The panel also touched on AI's potential in application development, with Tinybird experimenting in this area, noting the need for human oversight. -
+ +
[Watch the panel](https://www.youtube.com/watch?v=6_y0CUehlUA&t=3448s) **Panelists** + - [Fredrik Björk](https://twitter.com/fbjork), CEO at **Grafbase** - [Amy Dutton](https://twitter.com/selfteachme), Lead maintainer at **RedwoodJS** - [Alasdair Brown](https://github.com/sdairs), Head of DevRel at **Tinybird** @@ -104,9 +110,8 @@ The panel also touched on AI's potential in application development, with Tinybi Moderated by [Petra Donka](https://twitter.com/petradonka), Head of Developer Connections at **Prisma** +## Shaping the future -## Shaping the future -The Discover Data DX event was essential in establishing Data DX as an emerging category. It highlighted the industry alignment on the need for a more intuitive, efficient, and developer-focused approach to application development. +The Discover Data DX event was essential in establishing Data DX as an emerging category. It highlighted the industry alignment on the need for a more intuitive, efficient, and developer-focused approach to application development. Prisma will spearhead more initiatives to grow Data DX, driving innovation and collaboration in the industry. Learn more and stay updated at [datadx.io](https://www.datadx.io). - diff --git a/apps/blog/content/blog/datadx-manifesto-ikgyqj170k8h/index.mdx b/apps/blog/content/blog/datadx-manifesto-ikgyqj170k8h/index.mdx index 6613508725..5f22e2317d 100644 --- a/apps/blog/content/blog/datadx-manifesto-ikgyqj170k8h/index.mdx +++ b/apps/blog/content/blog/datadx-manifesto-ikgyqj170k8h/index.mdx @@ -17,13 +17,14 @@ Prisma presents the [Data DX manifesto](https://datadx.io), a transformative app ### Introducing the Data DX manifesto -The development landscape is ever-evolving, and with it, the challenges associated with data management, delivery, and usage. Recognizing the pressing need for a simple yet effective approach that addresses these challenges, we [introduced the Data DX concept](https://www.prisma.io/blog/cloudflare-partnership-qerefgvwirjq) when we announced our partnership with Cloudflare. +The development landscape is ever-evolving, and with it, the challenges associated with data management, delivery, and usage. Recognizing the pressing need for a simple yet effective approach that addresses these challenges, we [introduced the Data DX concept](https://www.prisma.io/blog/cloudflare-partnership-qerefgvwirjq) when we announced our partnership with Cloudflare. Today, we are following up on that announcement with the release of our complete thinking on the concept of the Data DX category in the form of the [Data DX Manifesto](https://datadx.io). The Data DX manifesto is a collaborative call to action, inviting developers and product creators to engage with this innovative philosophy. We wish for the community to start a dialogue and help guide and shape our thinking even further. The manifesto aims to: + - Outline benefits for developers who are building data-driven applications - Identify and recommend focus areas for products that fall into this new category - Act as a rallying cry to here @@ -36,8 +37,6 @@ For developers, it's about gaining a seamless and efficient pathway to handle da With the advent of Data DX, we aim to shed light on a prevalent challenge and propose a collective stride towards a solution. The manifesto serves as a starting point for a comprehensive discourse. - ### Embark on the Data DX Journey Whether you are a developer eager to adopt refined data handling methods or a creator aspiring to build products aligned with Data DX, a realm of possibilities awaits you at the [Manifesto website.](https://datadx.io) - diff --git a/apps/blog/content/blog/datadx-name-for-prismas-philosophy/index.mdx b/apps/blog/content/blog/datadx-name-for-prismas-philosophy/index.mdx index 529004708a..6a51e79ff1 100644 --- a/apps/blog/content/blog/datadx-name-for-prismas-philosophy/index.mdx +++ b/apps/blog/content/blog/datadx-name-for-prismas-philosophy/index.mdx @@ -15,22 +15,26 @@ Explore the evolution of [Data DX](https://www.datadx.io/) at Prisma, from its i In September 2023, Prisma launched a new category, Data DX, embodying a significant trend in the application development landscape. This initiative wasn't just about Prisma but signified a broader movement within the tech ecosystem. Data DX encapsulates the principles and practices aimed at enhancing the experience of developers working with data-driven applications. It's an approach that transcends specific tools or companies, embodying a philosophy that has been at the core of Prisma's operations since its early days under [Graphcool](https://graph.cool/). ## Our founder’s vision + Reflecting on his career shift from application development to building internal tooling, [Søren Bramer Schmidt](https://twitter.com/sorenbs)'s north star was always to simplify database interactions. He envisioned databases becoming as straightforward as creating a page in Notion, rather than a complex, fragile structure requiring constant attention. This vision was the seed that eventually blossomed into Data DX. Søren's personal philosophy was evident in Prisma's operations from the very beginning. Even when Prisma didn't explicitly name this approach, Data DX principles were already being practiced. The company's goal has consistently been to streamline and simplify how developers interact with databases, making it an intuitive and efficient process. ## Data DX in practice -Prisma's products are designed to alleviate the complexities typically associated with working with databases. Developers don't need to be experts in database scaling, indexes, or cluster management. Prisma's intuitive tools make it seamless to get started, increase the level of abstraction, and provide developer-friendly interfaces, making building with data more accessible and less daunting. + +Prisma's products are designed to alleviate the complexities typically associated with working with databases. Developers don't need to be experts in database scaling, indexes, or cluster management. Prisma's intuitive tools make it seamless to get started, increase the level of abstraction, and provide developer-friendly interfaces, making building with data more accessible and less daunting. Dedication to flexible accessibility, a core tenet of Data DX, means that Prisma is not only an easy-to-use solution for teams on day one, but is committed to remaining a reliable partner as teams scale up into production and enterprise. ## The ripple effect in the industry + [Data DX](https://www.datadx.io/) struck a chord across the ecosystem, with numerous companies such as [Cloudflare](https://www.cloudflare.com/), [Turso](https://turso.tech/), [Xata](https://xata.io/), [tinybird](https://www.tinybird.co/), [Grafbase](https://grafbase.com/), [PlanetScale](https://planetscale.com/), [Snaplet](https://www.snaplet.dev/), and [Supabase](https://supabase.com/) recognizing its value and standing with the manifesto’s principles as partners. This collective acknowledgment led to the establishment of Data DX as a distinct category as seen in the inaugural [Data DX event](https://www.datadx.io/event). Industry leaders have contributed to defining and enriching the concept of Data DX, highlighting its crucial role in supporting developers as they build effective, data-driven applications. ## Looking ahead: The future of Data DX -Prisma's commitment to Data DX continues to be a driving force in its ongoing innovation and expansion. In collaboration with partners, Data DX will continue to evolve, ensuring developer experience is at the forefront of product creation. The early adoption of Data DX signals a promising future where developers can engage with data in more meaningful, efficient, and creative ways. + +Prisma's commitment to Data DX continues to be a driving force in its ongoing innovation and expansion. In collaboration with partners, Data DX will continue to evolve, ensuring developer experience is at the forefront of product creation. The early adoption of Data DX signals a promising future where developers can engage with data in more meaningful, efficient, and creative ways. Data DX, as Søren aptly put, was always a part of Prisma's fabric—it just didn't have a label. Now, as an integral concept, it stands to simplify the complex, and enhance the overall experience of developing data-rich applications. diff --git a/apps/blog/content/blog/datamodel-v11-lrzqy1f56c90/index.mdx b/apps/blog/content/blog/datamodel-v11-lrzqy1f56c90/index.mdx index 2d65047687..b719c7bea2 100644 --- a/apps/blog/content/blog/datamodel-v11-lrzqy1f56c90/index.mdx +++ b/apps/blog/content/blog/datamodel-v11-lrzqy1f56c90/index.mdx @@ -51,14 +51,13 @@ We have also invested a lot into the introspection of existing databases, enabli With the old datamodel syntax, tables and columns are always named _exactly_ after the models and fields in your datamodel. Using the new `@db` directive, you can control what tables and columns should be called in the underlying database: - - ```graphql type User @db(name: "user") { id: ID! @id name: String! @db(name: "full_name") } ``` + ```ts CREATE TABLE "default$default"."user" ( "id" varchar(25) NOT NULL, @@ -67,7 +66,6 @@ CREATE TABLE "default$default"."user" ( ); ``` - In this case, the underlying table will be called `user` and the column `full_name`. ### Decide how a relation is represented in the database schema @@ -85,8 +83,6 @@ With the new datamodel, developers can take full control over expressing a relat Here is an example with two relations (one is _inline_, the other uses a _relation table_): - - ```graphql type User { id: ID! @id @@ -104,6 +100,7 @@ type Post { author: User! } ``` + ```ts CREATE TABLE "default$default"."User" ( "id" varchar(25) NOT NULL, @@ -127,7 +124,6 @@ CREATE TABLE "default$default"."_PostToUser" ( ); ``` - In the case of the inline relation, the placement of the `@relation(link: INLINE)` directive determines on which end of the relation the foreign key is being stored, in this example it's stored in the `User` table. ### Use any field as `id`, `createdAt` or `updatedAt` @@ -136,8 +132,6 @@ With the old datamodel, developers were required to use reserved fields if they With the new `@id`, `@createdAt` and `@updatedAt` directives, it is now possible to add this functionality to any field of a model: - - ```graphql type User { myID: ID! @id @@ -145,6 +139,7 @@ type User { myUpdatedAt: DateTime! @updatedAt } ``` + ```ts CREATE TABLE "test$devasdas"."User" ( "myID" varchar(25) NOT NULL, @@ -154,7 +149,6 @@ CREATE TABLE "test$devasdas"."User" ( ); ``` - ### More flexible IDs The current datamodel _always_ uses [CUIDs](https://github.com/ericelliott/cuid) to generate and store globally unique IDs for database records. The datamodel v1.1 now makes it possible to maintain custom IDs as well as to use other ID types (e.g. integers, sequences, or UUIDs). @@ -174,13 +168,10 @@ For more extensive tutorials and instructions for getting started with an existi To install the latest version of the Prisma CLI, run: - - ```shell npm install -g prisma ``` - > When running Prisma with Docker, you need to upgrade its Docker image to `1.31`. ### Option A: Upgrade from an older Prisma version @@ -207,32 +198,33 @@ type User { } type Profile { - id: ID! @unique - user: User! - bio: String! +id: ID! @unique +user: User! +bio: String! } type Post { - id: ID! @unique - createdAt: DateTime! - updatedAt: DateTime! - title: String! - published: Boolean! @default(value: "false") - author: User! - categories: [Category!]! +id: ID! @unique +createdAt: DateTime! +updatedAt: DateTime! +title: String! +published: Boolean! @default(value: "false") +author: User! +categories: [Category!]! } type Category { - id: ID! @unique - name: String! - posts: [Post!]! +id: ID! @unique +name: String! +posts: [Post!]! } enum Role { - USER - ADMIN +USER +ADMIN } -``` + +```` When using the old datamodel, the following tables are created by Prisma in the underlying database: - `User` @@ -272,31 +264,24 @@ services: user: prisma password: prisma port: '5432' -``` - +```` Now upgrade the running Prisma server: - - ```shell docker-compose up -d ``` - #### 3. Generate new datamodel via introspection If you're now running `prisma deploy`, your Prisma CLI will throw an error because you're trying to deploy a datamodel in the old syntax to an updated Prisma server. The easiest way to fix these errors is by generating a datamodel written in the new syntax via introspection. Run the following command inside the directory where your `prisma.yml` is located: - - ```shell prisma introspect ``` - This introspects your database and generates another datamodel with the new syntax, called `datamodel-TIMESTAMP.prisma` (e.g. `datamodel-1554394432089.prisma`). For the example from above, the following datamodel is generated: ```graphql @@ -342,19 +327,17 @@ enum Role { ADMIN } ``` + #### 4. Deploy new datamodel The final step is to delete the old `datamodel.prisma` file and rename your generated datamodel to `datamodel.prisma` (so that the `datamodel` property in your `prisma.yml` points to the generated file that's using the new syntax). Once that's done, you can run: - - ```shell prisma deploy ``` - #### 5. Optimize your database schema Because the introspection didn't change anything about your database layout, all relations are still represented as relation tables. If you want to learn how you can migrate the old 1:1 and 1:n relations to use _foreign keys_, check out the docs [here](https://v1.prisma.io/docs/1.31/releases-and-maintenance/features-in-preview/datamodel-v11-b6a7/#4.-optimizing-the-database-schema). @@ -367,13 +350,10 @@ After having learned how to upgrade existing Prisma projects, we'll now walk you Let's start by setting up a new Prisma project: - - ```shell prisma init hello-datamodel ``` - In the interactive wizard, select the following: 1. Select **Create new database** @@ -384,24 +364,20 @@ Before launching the Prisma server and the database via Docker, enable port mapp In the generated `docker-compose.yml`, uncomment the following lines in the Docker image configuration of the database: - - ```yml ports: - - '5432:5432' + - "5432:5432" ``` + ```yml ports: - - '3306:3306' + - "3306:3306" ``` - #### 2. Define datamodel Let's define a datamodel that takes advantage of the new Prisma features. Open `datamodel.prisma` and replace the contents with the following: - - ```graphql type User @db(name: "user") { id: ID! @id @@ -445,7 +421,6 @@ enum Role { } ``` - Here are some important bits about this datamodel definition: - Each model is mapped a table that's named after the model but lowercased using the `@db` directive. @@ -464,13 +439,10 @@ Here are some important bits about this datamodel definition: In the next step, Prisma will map this datamodel to the underlying database: - - ```shell prisma deploy ``` - ##### `Category` @@ -484,6 +456,7 @@ CREATE TABLE "hello-datamodel$dev"."category" ( PRIMARY KEY ("id") ); ``` + Index: | index_name | index_algorithm | is_unique | column_name | @@ -505,6 +478,7 @@ CREATE TABLE "hello-datamodel$dev"."post" ( PRIMARY KEY ("id") ); ``` + Index: | index_name | index_algorithm | is_unique | column_name | @@ -521,6 +495,7 @@ CREATE TABLE "hello-datamodel$dev"."post_to_category" ( "post" varchar(25) NOT NULL ); ``` + Index: | index_name | index_algorithm | is_unique | column_name | @@ -539,6 +514,7 @@ CREATE TABLE "hello-datamodel$dev"."profile" ( PRIMARY KEY ("id") ); ``` + Index: | index_name | index_algorithm | is_unique | column_name | @@ -560,12 +536,14 @@ CREATE TABLE "hello-datamodel$dev"."user" ( PRIMARY KEY ("id") ); ``` + Index: | index_name | index_algorithm | is_unique | column_name | | ---------------------------------------- | --------------- | --------- | ----------- | | `user_pkey` | `BTREE` | `TRUE` | `id` | | `hello-datamodel$dev.user.email._UNIQUE` | `BTREE` | `TRUE` | `email` | + @@ -588,4 +566,3 @@ While the new datamodel syntax already incorporates many features requested by o We are currently working on a new [data modeling language](https://github.com/prisma/specs/tree/master/schema) that will be a variation of the currently used [SDL](https://www.prisma.io/blog/graphql-sdl-schema-definition-language-6755bcb9ce51). We'd love hear what you think of the new datamodel. Please share your feedback by [opening an issue in the feedback repo](https://github.com/prisma/datamodel-v1.1-feedback/issues/new) or join the conversation on [Spectrum](https://spectrum.chat/prisma/general/releasing-prisma-v1-31~0d4dcb59-a58f-4ecf-84e6-b8509bad4abf). - diff --git a/apps/blog/content/blog/documenting-apis-mjjpZ7E7NkVP/index.mdx b/apps/blog/content/blog/documenting-apis-mjjpZ7E7NkVP/index.mdx index 3eaf6823d0..d0b277d719 100644 --- a/apps/blog/content/blog/documenting-apis-mjjpZ7E7NkVP/index.mdx +++ b/apps/blog/content/blog/documenting-apis-mjjpZ7E7NkVP/index.mdx @@ -17,10 +17,10 @@ Learn the importance of documenting web APIs, the different approaches available Application Programming Interfaces (APIs) are intermediaries that allow applications to communicate with each other. An example of this would be a frontend app retrieving data via the [GitHub API](https://docs.github.com/en/rest) to find out the number of stars a project has. -Web APIs (REST, GraphQL, gRPC) can send and receive data from other web APIs and frontend applications — from the web browser and mobile apps — over the [HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP) protocol using the client-server model. +Web APIs (REST, GraphQL, gRPC) can send and receive data from other web APIs and frontend applications — from the web browser and mobile apps — over the [HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP) protocol using the client-server model. This means a frontend application or web API (the client) sends a request to another web API (the server). The server replies with a response — data or an error — back to the client. -Before the client sends a request, it requires a number of details — the protocol, the URL/IP address where it's sending the request to, the structure of the request, route params and request queries, HTTP method and headers. +Before the client sends a request, it requires a number of details — the protocol, the URL/IP address where it's sending the request to, the structure of the request, route params and request queries, HTTP method and headers. The web API documentation provides these details for a developer to effectively use the API in their application. This article will discuss why you should document web APIs, the benefits, tools available, and bonus tools to improve web API development. @@ -30,7 +30,7 @@ This article will discuss why you should document web APIs, the benefits, tools API documentation is a _manual_ describing instructions on how to use an API — REST, gRPC or GraphQL. The documentation outlines the data structures, functions, arguments, return types, classes, etc., developers can refer to. -Building an API is half the job. An API lacking documentation would lead to friction between different teams building products. +Building an API is half the job. An API lacking documentation would lead to friction between different teams building products. Manual updates to documentation are a common problem that exists because they are prone to error. Failing to keep documentation up-to-date creates a point of failure for others who depend on it. - 13 min read - + 13 min read + - End-to-end type safety is implemented by ensuring the types across your entire application's stack are kept in sync. +End-to-end type safety is implemented by ensuring the types across your entire application's stack are kept in sync. ## Table Of Contents @@ -67,7 +67,7 @@ These are the main tools you will be using throughout this series: ### Assumed knowledge -While this series will attempt to cover everything in detail from a beginner's standpoint, the following would be helpful: +While this series will attempt to cover everything in detail from a beginner's standpoint, the following would be helpful: - Basic knowledge of JavaScript or TypeScript - Basic knowledge of GraphQL @@ -82,16 +82,17 @@ To follow along with the examples provided, you will be expected to have: ## Start a React application with Vite -There are many different ways to get started when building a React application. One of the easiest and most popular ways currently is to use [Vite](https://vitejs.dev/) to scaffold and set up your application. +There are many different ways to get started when building a React application. One of the easiest and most popular ways currently is to use [Vite](https://vitejs.dev/) to scaffold and set up your application. To get started, run this command in a directory where you would like your application's code to live: ```sh npm create vite@latest react-client -- --template react-ts ``` + > **Note**: You don't need to install any packages before running this command. -This command set up a ready-to-go React project in a folder named `react-client` using a TypeScript template. The template comes with a development server, hot module replacement, and a build process out of the box. +This command set up a ready-to-go React project in a folder named `react-client` using a TypeScript template. The template comes with a development server, hot module replacement, and a build process out of the box. Once your project has been generated you will be prompted to enter the new directory, install the node modules, and run the project. Go ahead and do that by running the following commands: @@ -100,6 +101,7 @@ cd react-client npm install npm run dev ``` + Once your development server is up and running you should see some output that looks similar to this: ![](/e2e-type-safety-graphql-react-1-I2GxIfxkSZ/imgs/localhost.png) @@ -123,13 +125,12 @@ Next, replace the contents of `/src/App.tsx` with the following component to giv // src/App.tsx function App() { - return ( -

Hello World!

- ) + return

Hello World!

; } -export default App +export default App; ``` + ## Set up TailwindCSS Your application will use [TailwindCSS](https://tailwindcss.com/) to make designing and styling your components easy. To get started, you will first need a few new dependencies: @@ -137,34 +138,35 @@ Your application will use [TailwindCSS](https://tailwindcss.com/) to make design ```sh npm install -D tailwindcss postcss autoprefixer ``` + The command above will install all of the pieces TailwindCSS requires to work in your project, including the Tailwind CLI. Initialize TailwindCSS in your project using the newly installed CLI: ```sh npx tailwindcss init -p ``` + This command created two files in your project: - `tailwind.config.cjs`: The configuration file for TailwindCSS -- `postcss.config.cjs`: The configuration file for PostCSS +- `postcss.config.cjs`: The configuration file for PostCSS Within `tailwind.config.cjs`, you will see a `content` key. This is where you will define which files in your project TailwindCSS should be aware of when scanning through your code and deciding which of its classes and utilities you are using. This is how TailwindCSS determines what needs to be bundled into its built and minified output. Add the following value to the `content` key's array to tell TailwindCSS to look at any `.tsx` file within the `src` folder: ```js -diff +diff; // tailwind.config.cjs module.exports = { - content: [ -+ "./src/**/*.tsx" - ], + content: [+"./src/**/*.tsx"], theme: { extend: {}, }, plugins: [], }; ``` + Finally, within `src/index.css` you will need to import the TailwindCSS utilities, which are required to use TailwindCSS in your project. Replace that entire file's contents with the following: ```css @@ -174,6 +176,7 @@ Finally, within `src/index.css` you will need to import the TailwindCSS utilitie @tailwind components; @tailwind utilities; ``` + TailwindCSS is now configured and ready to go! Replace the existing `

` tag in `src/App.tsx` with this JSX to test that the TailwindCSS classes are working: ```tsx @@ -185,6 +188,7 @@ TailwindCSS is now configured and ready to go! Replace the existing `

` tag i

// ... ``` + If your webpage looks like this, congrats! You've successfully set up TailwindCSS! ![](/e2e-type-safety-graphql-react-1-I2GxIfxkSZ/imgs/tailwind-complete.png) @@ -202,15 +206,17 @@ First, create a new file in the `src` directory named `types.ts`: ```sh touch src/types.ts ``` + This is the file where you will store all of the types this application needs. Within that file, add and export a new `type` named `Message` with a `string` field named `body`: ```ts // src/types.ts export type Message = { - body: string -} + body: string; +}; ``` + This type describes what will be available within a `Message` object. There is only one key, however in a real-world application this may contain dozens or more field definitions. Next, add and export another type named `User` with a `name` field of the `string` type and a `messages` field that holds an array of `Message` objects: @@ -221,10 +227,11 @@ Next, add and export another type named `User` with a `name` field of the `strin // ... export type User = { - name: string - messages: Message[] -} + name: string; + messages: Message[]; +}; ``` + > **Note**: In the next sections of this series, you will replace these manually written types with automatically generated ones that contain up-to-date representations of your API's exposed data model. Now that your data has been "described", head over to `src/App.tsx`. Here you will mock some data to play with in your application. @@ -234,10 +241,11 @@ First, import the new `User` type into `src/App.tsx`: ```tsx // src/App.tsx -import { User } from './types' +import { User } from "./types"; // ... ``` + Next, within the `App` function in that file, create a new variable named `users` that contains an array of `User` objects with a single user entry who has a couple of messages associated with it: ```tsx @@ -246,20 +254,26 @@ Next, within the `App` function in that file, create a new variable named `users // ... function App() { - const users: User[] = [{ - name: 'Prisma Fan', - messages: [{ - body: 'Prisma rocks!!' - }, { - body: 'Did I mention I love Prisma?' - }] - }] + const users: User[] = [ + { + name: "Prisma Fan", + messages: [ + { + body: "Prisma rocks!!", + }, + { + body: "Did I mention I love Prisma?", + }, + ], + }, + ]; // ... } -export default Apps +export default Apps; ``` + In the snippet above, you defined a single user who has two associated messages. This is all the data you will need to build the UI components for this application. ## Display a list of users @@ -269,52 +283,57 @@ The first piece of the UI you will build is the component that displays a user. ```sh mkdir src/components ``` + Inside of that folder, create a file named `UserDisplay.tsx`: ```sh touch src/components/UserDisplay.tsx ``` + This file wil contain the user display component. To start that component off create a function named `UserDisplay` that returns a simple `

` tag for now. Then export that function: ```tsx // src/components/UserDisplay.tsx function UserDisplay() { - return

User Component

+ return

User Component

; } -export default UserDisplay +export default UserDisplay; ``` -This will serve as the skeleton for your component. The goal here is to allow this component to take in a `user` parameter and display that user's data inside of the component. + +This will serve as the skeleton for your component. The goal here is to allow this component to take in a `user` parameter and display that user's data inside of the component. To accomplish this, first import your `User` type at the very top of `src/components/UserDisplay.tsx`: ```tsx // src/components/UserDisplay.tsx -import { User } from '../types' +import { User } from "../types"; // ... ``` -You will use this type to describe what a `user` property in your `UserDisplay` function should contain. + +You will use this type to describe what a `user` property in your `UserDisplay` function should contain. Add a new `type` to this file named `Props` with a single `user` field of the `User` type. Use that type to describe your function's arguments _(or "props")_: ```tsx // src/components/UserDisplay.tsx -import { User } from '../types' +import { User } from "../types"; type Props = { - user: User -} + user: User; +}; function UserDisplay({ user }: Props) { - return

User Component

+ return

User Component

; } -export default UserDisplay +export default UserDisplay; ``` + > **Note**: The `user` key is being [destructured](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) within the function arguments to allow easy access to its values. The `user` property allows you to provide your component an object of type `User`. Each user in this application will be displayed within a rectangle that contains the user's name. @@ -326,40 +345,44 @@ Replace the existing `

` tag with the following JSX to display a user's name w // ... function UserDisplay({ user }: Props) { - return

-
-

- {user.name} -

-
+ return ( +
+
+

{user.name}

+
+ ); } // ... ``` -This component is now ready to display a user's details, however you are not yet rendering it anywhere. + +This component is now ready to display a user's details, however you are not yet rendering it anywhere. Head over to `src/App.tsx` and import your new component. Then, in place of the current `

` tag, render the component for each user in your `users` array: ```tsx // src/App.tsx -import { User } from './types' -import UserDisplay from './components/UserDisplay' +import { User } from "./types"; +import UserDisplay from "./components/UserDisplay"; function App() { - const users: User[] = [/**/] + const users: User[] = [ + /**/ + ]; return (
- { - users.map((user, i) => ) - } + {users.map((user, i) => ( + + ))}
- ) + ); } -export default App +export default App; ``` + If you head back to your browser you should see a nice box displaying your user's name! The only thing missing at this point is the user's messages. ![](/e2e-type-safety-graphql-react-1-I2GxIfxkSZ/imgs/user-displayed.png) @@ -373,6 +396,7 @@ Start off by creating a component to display an individual message. Create a new ```sh touch src/components/MessageDisplay.tsx ``` + Then, import the `Message` type from `src/types.ts` into the new file and create a `Props` type with two keys: - `message`: A `Message` object that holds the message details @@ -383,13 +407,14 @@ The result should look like the snippet below: ```tsx // src/components/MessageDisplay.tsx -import { Message } from '../types' +import { Message } from "../types"; type Props = { - message: Message - index: number -} + message: Message; + index: number; +}; ``` + With those pieces in place, you are ready to build the component function. The code below uses the `Props` type you wrote to describe the function arguments, pulls out the `message` and `index` values using destructuring, renders the message in a styled container, and finally exports the component: ```tsx @@ -398,19 +423,20 @@ With those pieces in place, you are ready to build the component function. The c // ... function MessageDisplay({ message, index }: Props) { - return
-

- {message.body} -

+ return ( +
+

{message.body}

+ ); } -export default MessageDisplay +export default MessageDisplay; ``` + Now it's time to put that component to use! In `src/components/UserDisplay.tsx` import the `MessageDisplay` component and render one for each element in the `user.messages` array: ```tsx -diff +diff // src/components/UserDisplay.tsx +import MessageDisplay from './MessageDisplay' @@ -431,18 +457,20 @@ function UserDisplay({ user }: Props) { } // ... ``` + Over in your browser, you should now see each user's messages to their right! ![](/e2e-type-safety-graphql-react-1-I2GxIfxkSZ/imgs/messages-displayed.png) -That looks great, however there is one last thing to add. You are building a tree view, so the final piece is to render "branches" that connect each message to its user. +That looks great, however there is one last thing to add. You are building a tree view, so the final piece is to render "branches" that connect each message to its user. Create a new file in `src/components` named `Branch.tsx`: ```sh touch src/components/Branch.tsx ``` -This component will take in one property, `trunk`, which indicates whether or not the message it links to is the first in the list. + +This component will take in one property, `trunk`, which indicates whether or not the message it links to is the first in the list. > **Note**: This is why you needed the `index` key in the `MessageDisplay` component. @@ -452,11 +480,12 @@ Insert the following component into that file: // src/components/Branch.tsx function Branch({ trunk }: { trunk: boolean }) { - return
+ ); } -export default Branch +export default Branch; ``` + The snippet above renders a branch with some crafty TailwindCSS magic. If you are interested in what TailwindCSS has to offer or want to better understand what is going on above, TailwindCSS has amazing [docs](https://tailwindcss.com/docs/installation) that cover all of the classes used above. To finish off this application's UI, use the new `Branch` component within your `MessageDisplay` component to render a branch for each message: ```tsx -diff +diff // src/components/MessageDisplay.tsx import { Message } from '../types' @@ -495,6 +526,7 @@ function MessageDisplay({ message, index}: Props) { export default MessageDisplay ``` + Back over in your browser, you will now see branches for each message! Hover over a message to highlight the branch ✨ ![](/e2e-type-safety-graphql-react-1-I2GxIfxkSZ/imgs/finished-ui.png) diff --git a/apps/blog/content/blog/e2e-type-safety-graphql-react-2-j9mEyHY0Ej/index.mdx b/apps/blog/content/blog/e2e-type-safety-graphql-react-2-j9mEyHY0Ej/index.mdx index 54ba552972..ba2bebc484 100644 --- a/apps/blog/content/blog/e2e-type-safety-graphql-react-2-j9mEyHY0Ej/index.mdx +++ b/apps/blog/content/blog/e2e-type-safety-graphql-react-2-j9mEyHY0Ej/index.mdx @@ -17,10 +17,10 @@ tags: ---
- 10 min read -
+ 10 min read + - In this series you are learning how to implement end-to-end type safety using React, GraphQL, Prisma, and some other helpful tools that tie those three together. +In this series you are learning how to implement end-to-end type safety using React, GraphQL, Prisma, and some other helpful tools that tie those three together. ## Table Of Contents @@ -43,10 +43,9 @@ tags: - ## Introduction -In this section, you will set up all of the pieces needed to build a GraphQL API. You will start up a TypeScript project, provision a PostgreSQL database, initialize Prisma in your project, and finally seed your database. +In this section, you will set up all of the pieces needed to build a GraphQL API. You will start up a TypeScript project, provision a PostgreSQL database, initialize Prisma in your project, and finally seed your database. In the process, you will set up an important piece of the end-to-end type-safety puzzle: a source of truth for the shape of your data. @@ -72,7 +71,7 @@ These are the main tools you will be using throughout this series: ### Assumed knowledge -While this series will attempt to cover everything in detail from a beginner's standpoint, the following would be helpful: +While this series will attempt to cover everything in detail from a beginner's standpoint, the following would be helpful: - Basic knowledge of JavaScript or TypeScript - Basic knowledge of GraphQL @@ -95,12 +94,14 @@ To kick things off, create a new folder in your working directory that will cont ```shell mkdir graphql-server # Example folder ``` + This project will use [npm](https://www.npmjs.com/), a package manager for Node.js, to manage and install new packages. Navigate into your new folder and initialize npm using the following commands: ```shell cd graphql-server npm init -y ``` + ### Install the basic packages While building this API, you will install various packages that will help in the development of your application. For now, install the following development packages: @@ -112,6 +113,7 @@ While building this API, you will install various packages that will help in the ```shell npm i -D ts-node-dev typescript @types/node ``` + > **Note**: These dependencies were installed as development dependencies because they are only needed during development. None of them are part of the production deployment. ### Set up TypeScript @@ -121,6 +123,7 @@ With TypeScript installed in your project, you can now initialize the TypeScript ```shell npx tsc --init ``` + The above command will create a new file named `tsconfig.json` at the root of your project and comes with a default set of configurations for how to compile and handle your TypeScript code. For the purposes of this series, you will leave the default settings. @@ -130,17 +133,19 @@ Create a new folder named `src` and within that folder a new file named `index.t mkdir src touch src/index.ts ``` + This will be the entry point to your TypeScript code. Within that file, add a simple `console.log`: ```typescript // src/index.ts -console.log('Hey there! 👋'); +console.log("Hey there! 👋"); ``` + ### Add a development script In order to run your code, you will use `ts-node-dev`, which will compile and run your TypeScript code and watch for file changes. -When a file is changed in your application, it will re-compile and re-run your code. +When a file is changed in your application, it will re-compile and re-run your code. Within `package.json`, in the `"scripts"` section, add a new script named `"dev"` that uses `ts-node-dev` to run your entry file: @@ -154,16 +159,18 @@ Within `package.json`, in the `"scripts"` section, add a new script named `"dev" }, // ... ``` + You can now use the following command to run your code: ```shell npm run dev ``` + ![](/e2e-type-safety-graphql-react-2-j9mEyHY0Ej/imgs/run-dev.png) ## Set up the database -The next piece you will set up is the database. You will be using a PostgreSQL database for this application. There are many different ways to host and work with a +The next piece you will set up is the database. You will be using a PostgreSQL database for this application. There are many different ways to host and work with a PostgreSQL database, however, one of the simplest ways is to deploy your database using [Railway](https://railway.app/). Head over to [https://railway.app](https://railway.app) and, if you don't already have one, create an account. @@ -195,6 +202,7 @@ To set up Prisma, you first need to install Prisma CLI as a development dependen ```shell npm i -D prisma ``` + ### Initialize Prisma With Prisma CLI installed, you will have access to a set of useful tools and commands provided by Prisma. The command you will use here is called `init`, and will initialize Prisma in your project: @@ -202,6 +210,7 @@ With Prisma CLI installed, you will have access to a set of useful tools and com ```shell npx prisma init ``` + This command will create a new `prisma` folder within your project. Inside this folder you will find a file, `schema.prisma`, which contains the start of a Prisma schema. That file uses the Prisma Schema Language _(PSL)_ and is where you will define your database's tables and fields. It currently looks as follows: @@ -218,10 +227,12 @@ datasource db { url = env("DATABASE_URL") } ``` -Within the `datasource` block, note the `url` field. This fields equals a value `env("DATABASE_URL")`. This value tells Prisma to look within the environment variables for a + +Within the `datasource` block, note the `url` field. This fields equals a value `env("DATABASE_URL")`. This value tells Prisma to look within the environment variables for a variable named `DATABASE_URL` to find the database's connection string. ### Set the environment variable + `prisma init` also created a `.env` file for you with a single variable named `DATABASE_URL`. This variable holds the connection string Prisma will use to connect to your database. Replace the current default contents of that variable with the connection string you retrieved via the Railway UI: @@ -230,8 +241,9 @@ Replace the current default contents of that variable with the connection string # .env # Example: postgresql://postgres:Pb98NuLZM22ptNuR4Erq@containers-us-west-63.railway.app:6049/railway -DATABASE_URL="" +DATABASE_URL="" ``` + ### Model your data The application you are building will need two different database tables: `User` and `Message`. Each "user" will be able to have many associated "messages". @@ -255,6 +267,7 @@ model User { createdAt DateTime @default(now()) } ``` + Next, add a `Message` model with the following fields: - `id`: The unique ID of the database record @@ -270,10 +283,11 @@ model Message { createdAt DateTime @default(now()) } ``` + Finally, set up a one-to-many relation between the `User` and `Message` tables. ```prisma -diff +diff // prisma/schema.prisma model User { @@ -291,6 +305,7 @@ model Message { + user User @relation(fields: [userId], references: [id]) } ``` + This data modeling step is an important one. What you have done here is set up the _source of truth_ for the shape of your data. You database's schema is now defined in one central place, and used to generate a type-safe API that interacts with that database. @@ -305,8 +320,8 @@ Run the following command to create and apply a migration to your database: ```shell npx prisma migrate dev --name init ``` -The above command will create a new migration file named `init`, apply that migration to your database, and finally generate Prisma Client based off of that schema. +The above command will create a new migration file named `init`, apply that migration to your database, and finally generate Prisma Client based off of that schema. If you head back over to the Railway UI, in the **Data** tab you should see your tables listed. If so, the migration worked and your database is ready to be put to work! @@ -321,6 +336,7 @@ Within the `prisma` folder, create a new file named `seed.ts`: ```shell touch prisma/seed.ts ``` + Paste the following contents into that file: ```typescript @@ -385,6 +401,7 @@ main().then(() => { console.log("Data seeded..."); }); ``` + This script clears out the database and then creates three users. Each user is given two messages associated with it. > **Note**: In the next article, you will dive deeper into the process writing a few queries using Prisma Client. @@ -400,11 +417,13 @@ Now that the seed script is available, head over to your `package.json` file and }, // ... ``` + Use the following command to run your seed script: ```shell npx prisma db seed ``` + After running the script, if you head back to the Railway UI and into the **Data** tab, you should be able to navigate through the newly added data. ![](/e2e-type-safety-graphql-react-2-j9mEyHY0Ej/imgs/railway-data.png) diff --git a/apps/blog/content/blog/e2e-type-safety-graphql-react-3-fbV2ZVIGWg/index.mdx b/apps/blog/content/blog/e2e-type-safety-graphql-react-3-fbV2ZVIGWg/index.mdx index b85f2d7b9e..fe144fa403 100644 --- a/apps/blog/content/blog/e2e-type-safety-graphql-react-3-fbV2ZVIGWg/index.mdx +++ b/apps/blog/content/blog/e2e-type-safety-graphql-react-3-fbV2ZVIGWg/index.mdx @@ -17,10 +17,10 @@ tags: ---
- 14 min read -
+ 14 min read + - In this series you are learning how to implement end-to-end type safety using React, GraphQL, Prisma, and some other helpful tools that tie those three together. +In this series you are learning how to implement end-to-end type safety using React, GraphQL, Prisma, and some other helpful tools that tie those three together. ## Table Of Contents @@ -37,10 +37,9 @@ tags: - ## Introduction -In this section, you will build upon the project you set up in the previous article of this series by fleshing out a GraphQL API. +In this section, you will build upon the project you set up in the previous article of this series by fleshing out a GraphQL API. While building this API, you will focus on ensuring your interactions with the database, data handling within your resolvers, and data responses are all type-safe and that those types are in sync. @@ -66,7 +65,7 @@ These are the main tools you will be using throughout this series: ### Assumed knowledge -While this series will attempt to cover everything in detail from a beginner's standpoint, the following would be helpful: +While this series will attempt to cover everything in detail from a beginner's standpoint, the following would be helpful: - Basic knowledge of JavaScript or TypeScript - Basic knowledge of GraphQL @@ -78,19 +77,20 @@ To follow along with the examples provided, you will be expected to have: - [Node.js](https://nodejs.org) installed. - The [Prisma VSCode Extension](https://marketplace.visualstudio.com/items?itemName=Prisma.prisma) installed. _(optional)_ - - + + ## Start up a GraphQL Server -The very first thing you will need to build a GraphQL API is a running GraphQL server. In this application, you will use [GraphQL Yoga](https://www.graphql-yoga.com/) as your GraphQL server. +The very first thing you will need to build a GraphQL API is a running GraphQL server. In this application, you will use [GraphQL Yoga](https://www.graphql-yoga.com/) as your GraphQL server. Install the `@graphql-yoga/node` and `graphql` packages to get started: ```shell npm install @graphql-yoga/node graphql ``` -With those packages installed, you can now start up your own GraphQL server. Head over to `src/index.ts`. Replace the existing contents with the this snippet: + +With those packages installed, you can now start up your own GraphQL server. Head over to `src/index.ts`. Replace the existing contents with the this snippet: ```ts // src/index.ts @@ -98,28 +98,30 @@ With those packages installed, you can now start up your own GraphQL server. Hea // 1 import { createServer } from "@graphql-yoga/node"; // 2 -const port = Number(process.env.API_PORT) || 4000 +const port = Number(process.env.API_PORT) || 4000; // 3 const server = createServer({ - port + port, }); // 4 server.start().then(() => { console.log(`🚀 GraphQL Server ready at http://localhost:${port}/graphql`); }); ``` + The code above does the following: 1. Imports the `createServer` function from GraphQL Yoga 2. Creates a variable to hold the API's port, defaulting to `4000` if one is not present in the environment 3. Creates an instance of the GraphQL server -3. Starts the server up on port `4000` and lets the console know it's up and running +4. Starts the server up on port `4000` and lets the console know it's up and running If you start up your server, you will have access to a running _(empty)_ GraphQL API: ```shell npm run dev ``` + ![](/e2e-type-safety-graphql-react-3-fbV2ZVIGWg/imgs/yoga-start.png) > **Note**: The GraphQL server is up and running, however it is not usable because you have not yet defined any queries or mutations. @@ -131,19 +133,21 @@ GraphQL uses a strongly typed schema to define how a user can interact with the - Code-first: Your application code defines and generates a GraphQL schema - SDL-first: You manually write the GraphQL schema -In this application, you will take the code-first approach using a popular schema builder named [Pothos](https://pothos-graphql.dev). +In this application, you will take the code-first approach using a popular schema builder named [Pothos](https://pothos-graphql.dev). To get started with Pothos, you first need to install the core package: ```shell npm i @pothos/core ``` + Next, create an instance of the Pothos schema builder as a sharable module. Within the `src` folder, create a new file named `builder.ts` that will hold this module: ```shell cd src touch builder.ts ``` + For now, import the default export from the `@pothos/core` package and export an instance of it named `builder`: ```ts @@ -152,6 +156,7 @@ For now, import the default export from the `@pothos/core` package and export an import SchemaBuilder from "@pothos/core"; export const builder = new SchemaBuilder({}); ``` + ## Define a `Date` scalar type By default, GraphQL only supports a limited set of scalar data types: @@ -162,13 +167,14 @@ By default, GraphQL only supports a limited set of scalar data types: - Boolean - ID -If you think back to your Prisma schema, however, you will remember there are a few fields defined that use the `DateTime` data type. To handle those within your GraphQL API, you will need to define a custom `Date` scalar type. +If you think back to your Prisma schema, however, you will remember there are a few fields defined that use the `DateTime` data type. To handle those within your GraphQL API, you will need to define a custom `Date` scalar type. Fortunately, pre-made custom scalar type definitions are available thanks to the open-source community. The one you will use is called [`graphql-scalars`](https://www.graphql-scalars.dev/): ```shell npm i graphql-scalars ``` + You will need to register a `Date` scalar with your schema builder to let it know how to handle dates. The schema builder takes in a [generic](https://www.typescriptlang.org/docs/handbook/2/generics.html) where you can specify various [configurations](https://pothos-graphql.dev/docs/api/schema-builder#schematypes). Make the following changes to register the `Data` scalar type: @@ -190,13 +196,14 @@ export const builder = new SchemaBuilder<{ // 3 builder.addScalarType("Date", DateResolver, {}); ``` + Here's what changed in the snippet above. You: 1. Imported the `Date` scalar type's resolver which handles converting values to the proper date type within your API 2. Registered a new scalar type called `"Date"` using the `SchemaBuilder`'s `Scalars` configuration and configured the JavaScript types to use when accessing and validating fields of this type 3. Let the builder know how to handle the defined `Date` scalar type by providing the imported `DateResolver` -Within your GraphQL object types and resolvers, can now use the `Date` scalar type. +Within your GraphQL object types and resolvers, can now use the `Date` scalar type. ## Add the Pothos Prisma plugin @@ -211,10 +218,11 @@ First, install the plugin: ```shell npm i @pothos/plugin-prisma ``` + This plugin provides a Prisma generator that generates the types Pothos requires. Add the generator to your Prisma schema in `prisma/schema.prisma`: ```prisma -diff +diff // prisma/schema.prisma generator client { @@ -245,6 +253,7 @@ model Message { user User @relation(fields: [userId], references: [id]) } ``` + Once that is added, you will need a way to generate Pothos' artifacts. You will need to install this API's node modules and regenerate Prisma Client this each time this application is deployed later in the series, so go ahead and create a new `script` in `package.json` to handle this: ```json @@ -258,11 +267,13 @@ Once that is added, you will need a way to generate Pothos' artifacts. You will } } ``` + Now you can run that command to install your node modules and regenerate Prisma Client and the Pothos outputs: ```shell npm run build ``` + When you run the command above, you should see that Prisma Client and the Pothos integration were both generated. ![](/e2e-type-safety-graphql-react-3-fbV2ZVIGWg/imgs/generate.png) @@ -270,7 +281,7 @@ When you run the command above, you should see that Prisma Client and the Pothos Now that those types are generated, head over to `src/builder.ts`. Here you will import the `PrismaPlugin` and the generated Pothos types and apply them to your builder: ```ts -diff +diff // src/builder.ts import SchemaBuilder from "@pothos/core"; @@ -286,7 +297,8 @@ export const builder = new SchemaBuilder<{ builder.addScalarType("Date", DateResolver, {}); ``` -As soon as you add the generated types, you will notice a TypeScript error occur within the instantiation of the `SchemaBuilder`. + +As soon as you add the generated types, you will notice a TypeScript error occur within the instantiation of the `SchemaBuilder`. ![](/e2e-type-safety-graphql-react-3-fbV2ZVIGWg/imgs/prisma-type-required.png) @@ -295,7 +307,7 @@ Pothos is smart enough to know that, because you are using the Prisma plugin, yo For now, register the Prisma plugin and the generated types in the builder instance to let Pothos know about them: ```ts -diff +diff // src/builder.ts // ... @@ -310,13 +322,13 @@ export const builder = new SchemaBuilder<{ // ... ``` + You will, again, see a TypeScript error at this point. This is because the `builder` now expects an instance of Prisma Client to be provided to the function. ![](/e2e-type-safety-graphql-react-3-fbV2ZVIGWg/imgs/prisma-client-required.png) In the next step, you will instantiate Prisma Client and provide it here in the `builder`. - ## Create a reusable instance of Prisma Client You now need to create a re-usable instance of Prisma Client that will be used to query your database and provide the types required by the builder from the previous step. @@ -326,6 +338,7 @@ Create a new file in the `src` folder named `db.ts`: ```shell touch src/db.ts ``` + Within that file, import Prisma Client and create an instance of the client named `prisma`. Export that instantiated client: ```ts @@ -334,10 +347,11 @@ Within that file, import Prisma Client and create an instance of the client name import { PrismaClient } from "@prisma/client"; export const prisma = new PrismaClient(); ``` + Import the `prisma` variable into `src/builder.ts` and provide it to `builder` to get rid of the TypeScript error: ```ts -diff +diff // src/builder.ts // ... @@ -358,7 +372,8 @@ export const builder = new SchemaBuilder<{ // ... ``` -The Pothos Prisma plugin is now completely configured and ready to go. This takes the types generated by Prisma and allows you easy access to those within your GraphQL object types and queries. + +The Pothos Prisma plugin is now completely configured and ready to go. This takes the types generated by Prisma and allows you easy access to those within your GraphQL object types and queries. The cool thing about this is you now have a single source of truth (the Prisma schema) handling the types in your database, the API used to query the database, and the GraphQL schema. @@ -376,6 +391,7 @@ Create a new folder within `src` named `models`. Then create a `User.ts` file wi mkdir src/models touch src/models/User.ts ``` + This is where you will define the `User` object type and its related queries that you will expose through your GraphQL API. Import the `builder` instance: ```ts @@ -383,7 +399,8 @@ This is where you will define the `User` object type and its related queries tha import { builder } from "../builder"; ``` -Because you are using Pothos's Prisma plugin, the `builder` instance now has a method named [`prismaObject`](https://pothos-graphql.dev/docs/plugins/prisma#creating-types-with-builderprismaobject) you will use to define your object types. + +Because you are using Pothos's Prisma plugin, the `builder` instance now has a method named [`prismaObject`](https://pothos-graphql.dev/docs/plugins/prisma#creating-types-with-builderprismaobject) you will use to define your object types. That method takes in two parameters: @@ -393,13 +410,14 @@ That method takes in two parameters: Use that method to create a `"User"` type: ```ts -diff +diff; // src/models/User.ts import { builder } from "../builder"; -+builder.prismaObject("User", {}) ++builder.prismaObject("User", {}); ``` + > **Note**: If you press Ctrl + Space within an empty set of quotes before typing in the `name` field, you should get some nice auto-completion with a list of available models from your Prisma schema thanks to the Prisma plugin. > ![](/e2e-type-safety-graphql-react-3-fbV2ZVIGWg/imgs/model-auto.png) @@ -411,13 +429,14 @@ Within the `options` object, add a `fields` key that defines the `id`, `name` an import { builder } from "../builder"; builder.prismaObject("User", { - fields: t => ({ - id: t.exposeID("id"), - name: t.exposeString("name"), - messages: t.relation("messages") - }) -}) + fields: (t) => ({ + id: t.exposeID("id"), + name: t.exposeString("name"), + messages: t.relation("messages"), + }), +}); ``` + > **Note**: Hitting Ctrl + Space when you begin to type in a field name will give you a list of fields in the target model that match the data type of the "expose" function you are using. The function above defines a GraphQL type definition and registers it in the `builder` instance. Generating a schema from the `builder` does not actually store a GraphQL schema in your file system that you can check out, however the resulting type definition for your `User` will look like this: @@ -429,12 +448,14 @@ type User { name: String! } ``` + Next, add another file in the same folder named `Message.ts`: ```shell touch Message.ts ``` -This file will be similar to the `User.ts` file, except it will define the `Message` model. + +This file will be similar to the `User.ts` file, except it will define the `Message` model. Define the `id`, `body` and `createdAt` fields. Note the `createdAt` field has the `DateTime` type in your Prisma schema and will need a custom configuration to define the custom `date` scalar type you defined: @@ -453,6 +474,7 @@ builder.prismaObject("Message", { }), }); ``` + This function will result in the following GraphQL object type: ```graphql @@ -462,6 +484,7 @@ type Message { id: ID! } ``` + ## Implement your queries Currently, you have object types defined for your GraphQL schema, however you have not yet defined a way to actually access that data. To do this, you first need to initialize a [`Query` type](https://graphql.org/learn/schema/#the-query-and-mutation-types). @@ -475,6 +498,7 @@ At the bottom of your `src/builder.ts` file, intialize the `Query` type using `b builder.queryType({}); ``` + This registers a special GraphQL type that holds the definitions for each of your queries and acts as the entry point to your GraphQL API. You define this type in the `builder.ts` file to ensure the query builder has a `Query` type defined, that way you can add query fields to it later on. Within this `queryType` function, you have the ability to add query definitions directly, however, you will define these separately within your codebase to better organize your code. @@ -482,7 +506,7 @@ Within this `queryType` function, you have the ability to add query definitions Import the `prisma` instance into `src/models/User.ts`: ```ts -diff +diff // src/models/User.ts import { builder } from "../builder"; @@ -490,6 +514,7 @@ import { builder } from "../builder"; // ... ``` + Then, using the `builder`'s [`queryField`](https://pothos-graphql.dev/docs/api/schema-builder#queryfieldname-field) function, define a `"users"` query that exposes the `User` object type you defined: ```ts @@ -506,25 +531,27 @@ builder.queryField("users", (t) => resolve: async (query, root, args, ctx, info) => { return prisma.user.findMany({ ...query }); }, - }) + }), ); ``` + The snippet above: 1. Adds a field to the GraphQL schema's `Query` type named `"users"` 2. Defines a field that resolves to some type in your Prisma schema 3. Lets Pothos know this field will resolve to an array of your Prisma Client's `User` type -4. Sets up a resolver function for this field. +4. Sets up a resolver function for this field. > **Note**: The `resolve` function's `query` argument at the beginning of the argument list. This is a specific field Pothos populates when using `prismaField` function that is used to load data and relations in a performant way. This may be confusing if you come from a GraphQL background as it changes the expected order of arguments. -In order to better visualize what took place, here is the `Query` type and the `users` query that will be generated by the code in this section: +In order to better visualize what took place, here is the `Query` type and the `users` query that will be generated by the code in this section: ```graphql type Query { users: [User!]! } ``` + ## Apply the GraphQL schema You now have all of your GraphQL object types and queries defined and implemented. The last piece needed is a way to register all of these types and queries in a single place and generate the GraphQL schema based on your configurations. @@ -534,6 +561,7 @@ Create a new file in `src` named `schema.ts`: ```shell touch schema.ts ``` + This file will simply import the models, causing the code within the files to be run, and run the `builder` instance's `toSchema` function to generate the GraphQL schema: ```ts @@ -546,9 +574,8 @@ import "./models/User"; export const schema = builder.toSchema({}); ``` -The `toSchema` function generates an abstract syntax tree (AST) representation of your GraphQL schema. Below, you can see what the AST and GraphQL representations would look like: - +The `toSchema` function generates an abstract syntax tree (AST) representation of your GraphQL schema. Below, you can see what the AST and GraphQL representations would look like: ```graphql scalar Date @@ -569,6 +596,7 @@ type User { name: String! } ``` + ```json { __validationErrors: undefined, @@ -816,11 +844,10 @@ type User { } ``` - Over in your `src/index.ts` file, import the `schema` variable you just created. The `createServer` function's configuration object takes a key named `schema` that will accept the generated GraphQL schema: ```ts -diff +diff // src/index.ts import { createServer } from "@graphql-yoga/node"; @@ -837,6 +864,7 @@ server.start().then(() => { console.log(`🚀 GraphQL Server ready at http://localhost:${port}/graphql`); }); ``` + Fantastic! Your GraphQL schema has been defined using a code-first methodology, your GraphQL object and query types are in sync with your Prisma schema models, and your GraphQL server is being provided the generated GraphQL schema. At this point, run the server so you can play with the API: @@ -844,6 +872,7 @@ At this point, run the server so you can play with the API: ```sh npm run dev ``` + After running the above command, open up [http://localhost:4000/graphql](http://localhost:4000/graphql) in your browser to access the GraphQL playground. You should be presented with a page that looks like this: ![](/e2e-type-safety-graphql-react-3-fbV2ZVIGWg/imgs/playground.png) @@ -852,7 +881,7 @@ In the top-left corner of the screen, hit the **Explorer** button to see your AP ![](/e2e-type-safety-graphql-react-3-fbV2ZVIGWg/imgs/explorer.png) -If you click on the **users** query type, the right side of the screen will be automatically populated with a query for your user data. +If you click on the **users** query type, the right side of the screen will be automatically populated with a query for your user data. Run that query by hitting the "execute query" button to see the API in action: @@ -862,7 +891,7 @@ Feel free to play around with the different options to choose which fields you w ## Summary & What's next -In this article, you built out your entire GraphQL API. The API was built in a type-safe way by taking advantage of Prisma's generated types. These, along with the Pothos Prisma plugin, allowed you to ensure the types across your ORM, GraphQL object types, GraphQL query types, and resolvers were all in sync with the database schema. +In this article, you built out your entire GraphQL API. The API was built in a type-safe way by taking advantage of Prisma's generated types. These, along with the Pothos Prisma plugin, allowed you to ensure the types across your ORM, GraphQL object types, GraphQL query types, and resolvers were all in sync with the database schema. Along the way, you: diff --git a/apps/blog/content/blog/e2e-type-safety-graphql-react-4-JaHA8GbkER/index.mdx b/apps/blog/content/blog/e2e-type-safety-graphql-react-4-JaHA8GbkER/index.mdx index 54c87cff14..2ea38276f6 100644 --- a/apps/blog/content/blog/e2e-type-safety-graphql-react-4-JaHA8GbkER/index.mdx +++ b/apps/blog/content/blog/e2e-type-safety-graphql-react-4-JaHA8GbkER/index.mdx @@ -17,10 +17,10 @@ tags: ---
- 16 min read -
+ 16 min read + - In this series you are learning how to implement end-to-end type safety using React, GraphQL, Prisma, and some other helpful tools that tie those three together. +In this series you are learning how to implement end-to-end type safety using React, GraphQL, Prisma, and some other helpful tools that tie those three together. ## Table Of Contents @@ -66,7 +66,7 @@ These are the main tools you will be using throughout this series: ### Assumed knowledge -While this series will attempt to cover everything in detail from a beginner's standpoint, the following would be helpful: +While this series will attempt to cover everything in detail from a beginner's standpoint, the following would be helpful: - Basic knowledge of JavaScript or TypeScript - Basic knowledge of GraphQL @@ -78,8 +78,8 @@ To follow along with the examples provided, you will be expected to have: - [Node.js](https://nodejs.org) installed. - The [Prisma VSCode Extension](https://marketplace.visualstudio.com/items?itemName=Prisma.prisma) installed. _(optional)_ - - + + ## Set up GraphQL Codegen @@ -93,7 +93,7 @@ Your frontend project currently has a set of manually defined types, which were Until now, this worked fine. But what happens if a new field is introduced, updated, or removed form the API? Your frontend application would have no idea a change occurred in the API and the type definitions in the two projects would become out of sync. -How can you be sure a `user` object you retrieve over the network, for example, will contain all of the fields your React application is expecting? This is where [GraphQL Codegen](https://www.graphql-code-generator.com/) comes in: +How can you be sure a `user` object you retrieve over the network, for example, will contain all of the fields your React application is expecting? This is where [GraphQL Codegen](https://www.graphql-code-generator.com/) comes in: ![Fullstack Type Flow](/e2e-type-safety-graphql-react-4-JaHA8GbkER/imgs/1.png) @@ -107,25 +107,26 @@ So the entire flow of types across your application will be as follows: ### Installation - To get started, navigate into your React application's codebase via the terminal: ```sh cd react-client ``` + You will need a few different packages to set up GraphQL Codegen. Run the following to install the packages needed: ```sh npm i graphql npm i -D @graphql-codegen/cli @graphql-codegen/typed-document-node @graphql-codegen/typescript @graphql-codegen/typescript-operations ``` + Here's a brief overview of why each of these packages are needed: - [`graphql`](https://www.npmjs.com/package/graphql): The library that allows you to use GraphQL. - [`@graphql-codegen/cli`](https://www.graphql-code-generator.com/docs/getting-started/installation): The CLI tool that allows you to use different plugins to generate assets from a GraphQL API. - [`@graphql-codegen/typescript`](https://www.graphql-code-generator.com/plugins/typescript/typescript): The base plugin for GraphQL Codegen TypeScript-based plugins. This plugin takes your GraphQL API's schema and generates TypeScript types for each GraphQL type. -- [`@graphql-codegen/typescript-operations`](https://www.graphql-code-generator.com/plugins/typescript/typescript-operations): The GraphQL Codegen plugin that generates TypeScript types representing queries and responses based on queries you've written. -- [`@graphql-codegen/typed-document-node`](https://www.graphql-code-generator.com/plugins/typescript/typed-document-node): The GraphQL Codegen plugin that generates an Abstract Syntax Tree (AST) representation of any queries you've written. +- [`@graphql-codegen/typescript-operations`](https://www.graphql-code-generator.com/plugins/typescript/typescript-operations): The GraphQL Codegen plugin that generates TypeScript types representing queries and responses based on queries you've written. +- [`@graphql-codegen/typed-document-node`](https://www.graphql-code-generator.com/plugins/typescript/typed-document-node): The GraphQL Codegen plugin that generates an Abstract Syntax Tree (AST) representation of any queries you've written. > **Note**: Don't worry too much about the nitty-gritty of these plugins. Just know that they generate TypeScript types for each GraphQL object, query and mutation type in your GraphQL schema and help make your API request type-safe. @@ -138,6 +139,7 @@ At the root of your project, create a new file named `codegen.yml`. This will ho ```sh touch codegen.yml ``` + There will be three configurations to fill out in this file: 1. `schema`: The URL of your GraphQL schema @@ -149,14 +151,15 @@ There will be three configurations to fill out in this file: schema: http://localhost:4000/graphql documents: "./src/**/*.graphql" -generates: +generates: ./src/graphql/generated.ts: plugins: - typescript - typescript-operations - typed-document-node ``` -This configuration file lets GraphQL Codegen know a GraphQL schema is available at `localhost:4000/graphql`, where to find your queries, and where to output the generated types using all of the plugins you installed. + +This configuration file lets GraphQL Codegen know a GraphQL schema is available at `localhost:4000/graphql`, where to find your queries, and where to output the generated types using all of the plugins you installed. In order to actually generate the types, however, you will need to set up a script to run the generation command. Add the following script to `package.json`: @@ -171,7 +174,8 @@ In order to actually generate the types, however, you will need to set up a scri // ... } ``` -This provides a way for you to actually generate your types! You aren't quite ready yet, however. + +This provides a way for you to actually generate your types! You aren't quite ready yet, however. GraphQL Codegen won't be able to generate any types for your GraphQL queries if you don't have any queries! @@ -182,11 +186,13 @@ To keep things organized, you will write your queries in individual files within ```sh mkdir src/graphql ``` + You will only need one query for this application, which will retrieve the a list of users and their messages. Create a new file within the `graphql` directory named `users.query.graphql`: ```sh touch src/graphql/users.query.graphql ``` + Your applicaiton only needs a few pieces of information from the API: Each user's `name` and their messages `body` data. Write the following GraphQL query for that data: @@ -203,11 +209,11 @@ query GetUsers { } } ``` + ## Generate types using GraphQL Codegen Now that you have a query to work with, you can generate the types representing your query, the response, and the types available via your API! - Run the `script` you set up previously: > **Note**: Make sure your GraphQL API is up and running before running the command below! You can use `npm run dev` within the API's directory to start the server. @@ -215,6 +221,7 @@ Run the `script` you set up previously: ```sh npm run codegen ``` + You should see output similar to this: ![](/e2e-type-safety-graphql-react-4-JaHA8GbkER/imgs/codegen.png) @@ -223,8 +230,6 @@ As configured in your `codegen.yml` file, you will find a new file in `src/graph This file contains the generated types. Below are the types and objects generated from each plugin: - - ```ts export type Maybe = T | null; export type InputMaybe = Maybe; @@ -242,24 +247,25 @@ export type Scalars = { }; export type Message = { - __typename?: 'Message'; - body: Scalars['String']; - createdAt: Scalars['Date']; - id: Scalars['ID']; + __typename?: "Message"; + body: Scalars["String"]; + createdAt: Scalars["Date"]; + id: Scalars["ID"]; }; export type Query = { - __typename?: 'Query'; + __typename?: "Query"; users: Array; }; export type User = { - __typename?: 'User'; - id: Scalars['ID']; + __typename?: "User"; + id: Scalars["ID"]; messages: Array; - name: Scalars['String']; + name: Scalars["String"]; }; ``` + ```ts export type GetUsersQueryVariables = Exact<{ [key: string]: never }>; @@ -272,6 +278,7 @@ export type GetUsersQuery = { }>; }; ``` + ```ts export const GetUsersDocument = { kind: "Document", @@ -295,9 +302,7 @@ export const GetUsersDocument = { name: { kind: "Name", value: "messages" }, selectionSet: { kind: "SelectionSet", - selections: [ - { kind: "Field", name: { kind: "Name", value: "body" } }, - ], + selections: [{ kind: "Field", name: { kind: "Name", value: "body" } }], }, }, ], @@ -310,7 +315,6 @@ export const GetUsersDocument = { } as unknown as DocumentNode; ``` - These types and objects are exact representations of your GraphQL API and the queries you've written and are what will bridge the gap between your API and your client. ## Replace the manually entered types @@ -326,6 +330,7 @@ import type { GetUsersQuery } from "./graphql/generated"; // ... ``` + The reason you import this type instead of the full `User` and `Note` types is that the `GetUsersQuery` type has access to a more specific set of types that contain only the fields your query retrieves. Replace the existing types in that file with the following to expose the types representing your query results: @@ -337,13 +342,14 @@ import type { GetUsersQuery } from "./graphql/generated"; export type Message = GetUsersQuery["users"][0]["messages"][0]; export type User = GetUsersQuery["users"][0]; ``` + If you head over to `src/components/UserDisplay.tsx` and inspect the type being used for the `user` prop, you will now see it uses the type generated from your GraphQL query and API: ![](/e2e-type-safety-graphql-react-4-JaHA8GbkER/imgs/generated-types.png) You now have almost every piece of the end-to-end type-safety puzzle put in place. Your types are in sync from your database all the way to your frontend application. -The only thing missing is actually digesting your API rather than using static data. You will want to do this in a type-safe way to ensure you are querying only for data that exists in your API and retrieving all of the fields your frontend expects. +The only thing missing is actually digesting your API rather than using static data. You will want to do this in a type-safe way to ensure you are querying only for data that exists in your API and retrieving all of the fields your frontend expects. GraphQL Codegen already generated the types and query objects required to do this. You just need to use them! @@ -356,6 +362,7 @@ You will first need to install the dependency: ```sh npm i urql ``` + This library provides you with two exports you will need: A `Provider` component and a `createClient` function. You will need to use the `Provider` and `createClient` functions to provide urql to your application. In `src/main.tsx`, import those from the urql library: @@ -364,11 +371,12 @@ You will need to use the `Provider` and `createClient` functions to provide urql // src/main.tsx // ... -import { createClient, Provider } from 'urql'; +import { createClient, Provider } from "urql"; // ... ``` -Next, use the `createClient` function to create an instance of the urql client. The client takes in a configuration object with a `url` key, which points to your GraphQL API's url. + +Next, use the `createClient` function to create an instance of the urql client. The client takes in a configuration object with a `url` key, which points to your GraphQL API's url. While developing locally this should be `http://localhost:4000/graphql`, however once the API is deployed this will need to change. Use an environment variable allow you to provide an API url via the environment, while falling back to the localhost URL in development: @@ -378,35 +386,38 @@ While developing locally this should be `http://localhost:4000/graphql`, however // ... const client = createClient({ - url: import.meta.env.VITE_API_URL || 'http://localhost:4000/graphql', + url: import.meta.env.VITE_API_URL || "http://localhost:4000/graphql", }); // ... ``` + The last step to provide urql to your application is to wrap your `App` component in the urql `Provider` component and pass that component the instantiated client: ```tsx -diff +diff; // src/main.tsx -import React from 'react' -import ReactDOM from 'react-dom/client' -import App from './App' -import './index.css' -import { createClient, Provider } from 'urql'; +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App"; +import "./index.css"; +import { createClient, Provider } from "urql"; const client = createClient({ - url: import.meta.env.VITE_API_URL || 'http://localhost:4000/graphql', + url: import.meta.env.VITE_API_URL || "http://localhost:4000/graphql", }); -ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( +ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( -+ - -+ - -) + +{" "} + + +{" "} + + , +); ``` + ## Query your data You can now use urql to query your data! Head over to `src/App.tsx` and import the `useQuery` function from urql. Also import the `GetUsersDocument` object from `graphql/generated.ts`, as this will contain the AST representation of your query: @@ -416,11 +427,12 @@ You can now use urql to query your data! Head over to `src/App.tsx` and import t // ... -import { useQuery } from 'urql' -import { GetUsersDocument } from './graphql/generated' +import { useQuery } from "urql"; +import { GetUsersDocument } from "./graphql/generated"; // ... ``` + Within the `App` function, you can now replace the static variable and data with the following query: ```tsx @@ -430,41 +442,43 @@ Within the `App` function, you can now replace the static variable and data with function App() { const [results] = useQuery({ - query: GetUsersDocument - }) + query: GetUsersDocument, + }); // ... } // ... ``` -This uses the `GetUserDocument` query object to request data from your API and return it in a properly typed variable. + +This uses the `GetUserDocument` query object to request data from your API and return it in a properly typed variable. You no longer need the `User` type import because the typing is already being specified in the `GetUsersDocument` object. You will also need to adjust the code used to map over each user in the JSX, as the query results are now returned in a nested object. The resulting file should look as follows: ```tsx // src/App.tsx -import UserDisplay from './components/UserDisplay' -import { useQuery } from 'urql' -import { GetUsersDocument } from './graphql/generated' +import UserDisplay from "./components/UserDisplay"; +import { useQuery } from "urql"; +import { GetUsersDocument } from "./graphql/generated"; function App() { const [results] = useQuery({ - query: GetUsersDocument - }) + query: GetUsersDocument, + }); return (
- { - results.data?.users.map((user, i) => ) - } + {results.data?.users.map((user, i) => ( + + ))}
- ) + ); } -export default App +export default App; ``` + Notice your API request results are properly typed based off of the types within the API itself! If both your API and Client are running, head over to the browser. You should now see all of your data! ![](/e2e-type-safety-graphql-react-4-JaHA8GbkER/imgs/finished.png) @@ -493,16 +507,15 @@ You will need to retrieve the SSH url for this repository to use later on. Grab Now within your React application, run the follwing command to initialize and push a local repository, replacing `` with the SSH url: - - ```sh git init git add . git commit -m "first commit" git branch -M main -git remote add origin +git remote add origin git push -u origin main ``` + ```sh git init git add . @@ -512,7 +525,6 @@ git remote add origin git@github.com:sabinadams/e2e-type-safety-client.git git push -u origin main ``` - Next, you will repeat these steps for your API's codebase. Create another new repository from your Github dashboard: @@ -528,16 +540,15 @@ You should again see a page with some setup instructions. Grab the SSH url from Finally, navigate via the terminal into your GraphQL API's codebase and run the following set of commands. Again, replace `` with your SSH url: - - ```sh git init git add . git commit -m "first commit" git branch -M main -git remote add origin +git remote add origin git push -u origin main ``` + ```sh git init git add . @@ -547,12 +558,11 @@ git remote add origin git@github.com:sabinadams/e2e-type-safety-api.git git push -u origin main ``` - ## Deploy the API Now that your code is available on Github, you can deploy your codebases! -Head over to [Render](https://render.com) and create a free account if you do not already have one. +Head over to [Render](https://render.com) and create a free account if you do not already have one. The first thing you will deploy is your GraphQL API. On your dashboard, hit the **New Web Service** button, which will allow you to deploy a Node.js application: @@ -589,7 +599,6 @@ Finally, at the bottom of the page, hit the **Create Web Service** button: This will trigger the deployment process! Once that finishes deploying, you will be able to access the URL Render provides to see your GraphQL API. - Copy the URL from the location shown below and navigate to it in a new browser window at the `/graphql` route: ![](/e2e-type-safety-graphql-react-4-JaHA8GbkER/imgs/api-url.png) @@ -607,9 +616,9 @@ Connect this static site to your React application's Github repostory. You will be prompted again to fill out some details for deploying this application: 1. **name**: Pick any name you'd like -4. **Branch**: `main` -5. **Build Command**: `npm run build` -6. **Publish directory**: `dist` +2. **Branch**: `main` +3. **Build Command**: `npm run build` +4. **Publish directory**: `dist` Under the **Advanced** section, add an environment variable named `VITE_API_URL` whose value is the URL of your deployed GraphQL API at the `/graphql` route. For example: diff --git a/apps/blog/content/blog/elsevier-customer-story-SsAASKagMHtN/index.mdx b/apps/blog/content/blog/elsevier-customer-story-SsAASKagMHtN/index.mdx index 7adf6122c5..a6627d8307 100644 --- a/apps/blog/content/blog/elsevier-customer-story-SsAASKagMHtN/index.mdx +++ b/apps/blog/content/blog/elsevier-customer-story-SsAASKagMHtN/index.mdx @@ -19,15 +19,15 @@ With the help of Prisma, Elsevier is in the process of modernizing the scientifi ## Contributing to advances in science and healthcare -[Elsevier's](https://www.elsevier.com/) mission of helping researchers and healthcare professionals is rooted in publishing and has also evolved into a global leader in information and analytics. With so much health-related information being shared in real time, Elsevier decided it was time to modernize and speed up their existing manual peer review process. +[Elsevier's](https://www.elsevier.com/) mission of helping researchers and healthcare professionals is rooted in publishing and has also evolved into a global leader in information and analytics. With so much health-related information being shared in real time, Elsevier decided it was time to modernize and speed up their existing manual peer review process. ![Peer Review Workflow](/elsevier-customer-story-SsAASKagMHtN/imgs/peer-review-diagram-V3.png) -Building an application to speed up the peer review process would help Elsevier remain a leader in healthcare research. They dedicated a small project team consisting of Serghei Ghidora (Tech Lead), Paul Foeckler (Product Owner), and a UX Designer to develop a minimum viable product (MVP) to make the peer review process faster and more efficient. +Building an application to speed up the peer review process would help Elsevier remain a leader in healthcare research. They dedicated a small project team consisting of Serghei Ghidora (Tech Lead), Paul Foeckler (Product Owner), and a UX Designer to develop a minimum viable product (MVP) to make the peer review process faster and more efficient. ## Setting a strong foundation with Prisma -Streamlining a very manual, logically complex publication process is a tall task. Serghei knew that being flexible was going to be key to developing a successful MVP. +Streamlining a very manual, logically complex publication process is a tall task. Serghei knew that being flexible was going to be key to developing a successful MVP. -"When it comes to the data model experimentation, handling migrations and things like that is just amazing. That you are able to add something or remove something in Prisma, and you run the migrations and Prisma will do everything by itself." +"When it comes to the data model experimentation, handling migrations and things like that is just amazing. That you are able to add something or remove something in Prisma, and you run the migrations and Prisma will do everything by itself." @@ -109,8 +107,6 @@ Serghei understood the importance of selecting technologies that were going to a The flexibility proved to be a major contributor to a single tech lead producing a meaningful product in just ten months. - - ## The application's architecture In addition to Prisma, Serghei utilized several other technologies to achieve their MVP. The project structure looks like the following, with Prisma serving types to multiple apps. diff --git a/apps/blog/content/blog/enabling-cors-for-express-graphql-apollo-server-1ef999bfb38d/index.mdx b/apps/blog/content/blog/enabling-cors-for-express-graphql-apollo-server-1ef999bfb38d/index.mdx index c63e4480c4..a80d1e05a4 100644 --- a/apps/blog/content/blog/enabling-cors-for-express-graphql-apollo-server-1ef999bfb38d/index.mdx +++ b/apps/blog/content/blog/enabling-cors-for-express-graphql-apollo-server-1ef999bfb38d/index.mdx @@ -33,76 +33,78 @@ In fact, we prepared an [example](https://github.com/nikolasburk/graphql-cors-ex ### Server ```js -const express = require('express') -const bodyParser = require('body-parser') -const cors = require('cors') -const { graphqlExpress, graphiqlExpress } = require('apollo-server-express') -const { makeExecutableSchema } = require('graphql-tools') +const express = require("express"); +const bodyParser = require("body-parser"); +const cors = require("cors"); +const { graphqlExpress, graphiqlExpress } = require("apollo-server-express"); +const { makeExecutableSchema } = require("graphql-tools"); const typeDefs = ` type Query { hello(name: String): String! } -` +`; const resolvers = { Query: { - hello: (_, { name }) => `Hello ${name || 'World'}`, + hello: (_, { name }) => `Hello ${name || "World"}`, }, -} +}; -const myGraphQLSchema = makeExecutableSchema({ typeDefs, resolvers }) -const PORT = 4000 -const app = express() +const myGraphQLSchema = makeExecutableSchema({ typeDefs, resolvers }); +const PORT = 4000; +const app = express(); // app.use(cors()) // not having cors enabled will cause an access control error -app.use('/graphql', bodyParser.json(), graphqlExpress({ schema: myGraphQLSchema })) -app.get('/graphiql', graphiqlExpress({ endpointURL: '/graphql' })) +app.use("/graphql", bodyParser.json(), graphqlExpress({ schema: myGraphQLSchema })); +app.get("/graphiql", graphiqlExpress({ endpointURL: "/graphql" })); -console.log(`Server listening on http://localhost:${PORT} ...`) -app.listen(PORT) +console.log(`Server listening on http://localhost:${PORT} ...`); +app.listen(PORT); ``` + > Notice that line 23 is commented out — CORS is not enabled! ### Frontend ```js -import React from 'react' -import ReactDOM from 'react-dom' -import './index.css' -import App from './App' -import registerServiceWorker from './registerServiceWorker' -import { ApolloProvider } from 'react-apollo' -import { ApolloClient, HttpLink, InMemoryCache } from 'apollo-client-preset' +import React from "react"; +import ReactDOM from "react-dom"; +import "./index.css"; +import App from "./App"; +import registerServiceWorker from "./registerServiceWorker"; +import { ApolloProvider } from "react-apollo"; +import { ApolloClient, HttpLink, InMemoryCache } from "apollo-client-preset"; -const httpLink = new HttpLink({ uri: 'http://localhost:4000/graphql' }) +const httpLink = new HttpLink({ uri: "http://localhost:4000/graphql" }); const client = new ApolloClient({ link: httpLink, cache: new InMemoryCache(), -}) +}); ReactDOM.render( , - document.getElementById('root'), -) -registerServiceWorker() + document.getElementById("root"), +); +registerServiceWorker(); ``` + > Standard setup for using Apollo Client 2.0 ```js -import React, { Component } from 'react' -import logo from './logo.svg' -import './App.css' -import gql from 'graphql-tag' -import { graphql } from 'react-apollo' +import React, { Component } from "react"; +import logo from "./logo.svg"; +import "./App.css"; +import gql from "graphql-tag"; +import { graphql } from "react-apollo"; class App extends Component { render() { if (this.props.data.loading) { - return
Loading
+ return
Loading
; } return (
@@ -110,18 +112,17 @@ class App extends Component {

{this.props.data.hello}

- ) + ); } } -export default graphql( - gql` - { - hello - } - `, -)(App) +export default graphql(gql` + { + hello + } +`)(App); ``` + > The `App` component sends a simple `hello` query using Apollo Client In your local development setup, where the React app is loaded from [`http://localhost:3000`](http://localhost:3000) and the GraphQL server is serving at [`http://localhost:4000`](http://localhost:4000)/graphql, you’ll now get an access control error if you’re trying to run the app: @@ -150,12 +151,12 @@ When using `express-graphql` and `apollo-server`, all you need to do is include ```js // other imports ... -const cors = require('cors') +const cors = require("cors"); -const app = express() +const app = express(); -app.use(cors()) // enable `cors` to set HTTP response header: Access-Control-Allow-Origin: * -app.use('/graphql', bodyParser.json(), graphqlExpress({ schema })) +app.use(cors()); // enable `cors` to set HTTP response header: Access-Control-Allow-Origin: * +app.use("/graphql", bodyParser.json(), graphqlExpress({ schema })); -app.listen(PORT) -``` \ No newline at end of file +app.listen(PORT); +``` diff --git a/apps/blog/content/blog/esop-exercise-windows/index.mdx b/apps/blog/content/blog/esop-exercise-windows/index.mdx index a43eec6958..d02908595f 100644 --- a/apps/blog/content/blog/esop-exercise-windows/index.mdx +++ b/apps/blog/content/blog/esop-exercise-windows/index.mdx @@ -11,11 +11,11 @@ heroImagePath: "/esop-exercise-windows/imgs/hero-187cb6c0e9ac7a4c48892a3e196329e heroImageAlt: "Extended ESOP Exercise Windows" --- -As a progressive OSS SaaS company, we are expanding our commitment to transparency by sharing insights into our operational practices, starting with our approach to stock options. +As a progressive OSS SaaS company, we are expanding our commitment to transparency by sharing insights into our operational practices, starting with our approach to stock options. This move reflects our belief that sharing knowledge about equitable employee compensation is as vital as sharing code, offering a blueprint for building sustainable, employee-centric businesses. Stay tuned as we embark on this expanded journey of openness, hoping to inspire and inform alike. -Employee Stock Options Programs (ESOPs) are commonplace in the startup ecosystem, with nearly every startup in the USA and Europe, including Prisma, implementing some form of ESOP. They evoke a wide range of opinions and emotions, and their consequences have become a source of folklore and myths. Some perceive ESOPs as a means to extraordinary wealth, while others regard them as mere lottery tickets or tools to persuade employees to accept lower salaries. These differing views are a reflection of the reality shaped by the design of ESOPs. +Employee Stock Options Programs (ESOPs) are commonplace in the startup ecosystem, with nearly every startup in the USA and Europe, including Prisma, implementing some form of ESOP. They evoke a wide range of opinions and emotions, and their consequences have become a source of folklore and myths. Some perceive ESOPs as a means to extraordinary wealth, while others regard them as mere lottery tickets or tools to persuade employees to accept lower salaries. These differing views are a reflection of the reality shaped by the design of ESOPs. This blog post aims to discuss the concept of 90-day exercise windows in programs and explain why we decided to extend this period to 10 years at Prisma. We believe that this change is not only fair to our team, but also a good business decision. @@ -41,26 +41,25 @@ Our 10-year exercise window reinforces this belief by providing past team member - time for more information to be gained about Prisma’s prospective success and thereby - reducing the overall risk of the investment. -Finally, this change benefited many of our team members based in Berlin, Germany, where we previously had our largest office. Unfavorable German tax laws affected a significant number of our team, and this adjustment helped them avoid extraordinarily high tax payments if they were to exercise their options upon departure. +Finally, this change benefited many of our team members based in Berlin, Germany, where we previously had our largest office. Unfavorable German tax laws affected a significant number of our team, and this adjustment helped them avoid extraordinarily high tax payments if they were to exercise their options upon departure. -Although extended exercise periods are popular with employees, there are ample critics who have valid concerns about this approach, as illustrated by [this post](https://a16z.com/the-lack-of-options-for-startup-employees-options/) from Andreessen Horowitz. +Although extended exercise periods are popular with employees, there are ample critics who have valid concerns about this approach, as illustrated by [this post](https://a16z.com/the-lack-of-options-for-startup-employees-options/) from Andreessen Horowitz. ## Criticism of extended exercise windows -From a *fairness* perspective, critics have pointed out that the longer exercise period will perpetuate a *wealth transfer*. Former team members, who are no longer contributing to the company can hold on to their options for an extended period, benefiting from the work of current team members. +From a _fairness_ perspective, critics have pointed out that the longer exercise period will perpetuate a _wealth transfer_. Former team members, who are no longer contributing to the company can hold on to their options for an extended period, benefiting from the work of current team members. -From an *economics* perspective, it is argued that the prolonged exercise window may lead to an increased rate of dilution for current employees. +From an _economics_ perspective, it is argued that the prolonged exercise window may lead to an increased rate of dilution for current employees. -> *Dilution is the decrease in ownership percentage for existing shareholders when a company issues or reserves new shares of stock. —* ([Carta](https://carta.com/blog/how-to-manage-equity-dilution-as-an-early-stage-startup/)) -> +> _Dilution is the decrease in ownership percentage for existing shareholders when a company issues or reserves new shares of stock. —_ ([Carta](https://carta.com/blog/how-to-manage-equity-dilution-as-an-early-stage-startup/)) -It is argued that dilution would be amplified because of former employees retaining their options, forcing the company to refresh the option pool more frequently to attract new talent or incentivize existing team-members. This dilution can impact the ownership percentage of current team members in the company. +It is argued that dilution would be amplified because of former employees retaining their options, forcing the company to refresh the option pool more frequently to attract new talent or incentivize existing team-members. This dilution can impact the ownership percentage of current team members in the company. These concerns are not entirely wrong, and could be mitigated by a change in perspective, and some adjusted financial modelling. Below are a few perspectives we hod at Prisma seeks a compromise between the interests of current team-members, former team-members, founders, investors, and other shareholders. ### About fairness: Standing on the shoulders of giants -The concern raised by critics about *"wealth transfer"* overlooks a fundamental issue. Many new and current team members can only join a company because of the success achieved by those who came before them. It also seems unfair that a team-member who joined when the startup was just starting out should have to take a much bigger financial risk to make money from their stock options compared to an employee who joined later. One way to address this is by offering larger stock option grants to early-stage employees, typically coupled with a lower exercise price. This is intended to serve as compensation for the risk involved. +The concern raised by critics about _"wealth transfer"_ overlooks a fundamental issue. Many new and current team members can only join a company because of the success achieved by those who came before them. It also seems unfair that a team-member who joined when the startup was just starting out should have to take a much bigger financial risk to make money from their stock options compared to an employee who joined later. One way to address this is by offering larger stock option grants to early-stage employees, typically coupled with a lower exercise price. This is intended to serve as compensation for the risk involved. However, some people argue that employees in the early stages should accept lower salaries in exchange for these bigger stock option grants. This means that early-stage employees have to accept both lower salaries and more investment risk. It is not clear whether the larger stock option grants and exercise price they receive are enough to make up for these sacrifices. @@ -68,7 +67,7 @@ Resolving fairness problems is always difficult. Having longer exercise periods ### About economics: Mitigating dilution through increased present value -The second concern — the economics of extended exercise windows and its effect on dilution — is also not complete. By considering the *Present Value* (PV) of stock options, a different approach can be pursued. +The second concern — the economics of extended exercise windows and its effect on dilution — is also not complete. By considering the _Present Value_ (PV) of stock options, a different approach can be pursued. Specifically, extending the exercise window for stock options increases the chances for team members to benefit from their options. This is because the probability of a liquidity event (IPO, purchase, secondary sale, etc.) increases with time. Due to the time value of money, the PV of stock options with a longer (e.g. 10-year) exercise window would therefore be more than the PV of stock options with a shorter (e.g. 90-day) exercise window - all things being equal. @@ -76,7 +75,7 @@ Due to the higher PV, startups could justifiably reduce the size of stock option By reducing the size of stock option grants and increasing the exercise windows, more employees will be able to benefit from their stock options, although the size of the individual grants would be smaller. This more equitable distribution would eliminate the stark contrast between employees who can exercise their stock options and those who cannot. This would ultimately increase the perceived value of Employee Stock Ownership Plans (ESOPs) as more individuals benefit from them. Additionally, this could enhance the overall perceived value of compensation packages without incurring any cost to companies. -Following this, there is a strong business case to increase exercise windows to benefit from the economics. The question is really not *if* this should be done, but rather *how* this should be done. 10 Year exercise windows are not always the best option. It will depend on the *Startup* to *IPO* timeframe a company expects, among other factors. In addition, arriving at a Present Value would require the company to make some impossible assumptions about the *Future Value* (FV), and the *Rate of Return* (r). Ultimately, the idea is not to try and engage in detailed financial modeling but rather to implement an approach that approximates future expectations and changes over time as more information becomes available. +Following this, there is a strong business case to increase exercise windows to benefit from the economics. The question is really not _if_ this should be done, but rather _how_ this should be done. 10 Year exercise windows are not always the best option. It will depend on the _Startup_ to _IPO_ timeframe a company expects, among other factors. In addition, arriving at a Present Value would require the company to make some impossible assumptions about the _Future Value_ (FV), and the _Rate of Return_ (r). Ultimately, the idea is not to try and engage in detailed financial modeling but rather to implement an approach that approximates future expectations and changes over time as more information becomes available. ## Unintended consequences diff --git a/apps/blog/content/blog/formbricks-and-prisma-accelerate-solving-scalability-together/index.mdx b/apps/blog/content/blog/formbricks-and-prisma-accelerate-solving-scalability-together/index.mdx index e5ca1bad32..6ed4579a5c 100644 --- a/apps/blog/content/blog/formbricks-and-prisma-accelerate-solving-scalability-together/index.mdx +++ b/apps/blog/content/blog/formbricks-and-prisma-accelerate-solving-scalability-together/index.mdx @@ -36,13 +36,17 @@ The implementation of Prisma Accelerate came at a crucial time. Formbricks had j The transition to Prisma Accelerate happened during a night of intense work, set against the backdrop of trying to fix a failing database at 3 a.m. in South Korea. Despite the challenging circumstances, setting up Accelerate was straightforward. - - It's also possible to set up Accelerate when you're totally tired at 3 a.m. in the morning. The process was pretty straightforward – created an account, replaced the connection string, and connected the database on the Prisma Accelerate website. It was easy and worked out, even under those high-pressure circumstances. + It's also possible to set up Accelerate when you're totally tired at 3 a.m. in the morning. The + process was pretty straightforward – created an account, replaced the connection string, and + connected the database on the Prisma Accelerate website. It was easy and worked out, even under + those high-pressure circumstances. ## The impact of Prisma Accelerate @@ -55,6 +59,6 @@ For Formbricks, a platform where real-time user feedback is crucial, the ability Formbricks' journey with Prisma Accelerate is a clear example of how the right technological partnership alongside solving immediate problems, can set a foundation for future growth and stability. As they continue to evolve and expand, the scalability and efficiency provided by Prisma Accelerate will remain a key component of their success. ------ +--- Interested in trying Prisma Accelerate for yourself? Head over to our [Platform Console](https://console.prisma.io/login?source=blog?medium=customer-story-formbricks) to get started! diff --git a/apps/blog/content/blog/from-rust-to-typescript-a-new-chapter-for-prisma-orm/index.mdx b/apps/blog/content/blog/from-rust-to-typescript-a-new-chapter-for-prisma-orm/index.mdx index 2d7b697ad8..51a29a68ba 100644 --- a/apps/blog/content/blog/from-rust-to-typescript-a-new-chapter-for-prisma-orm/index.mdx +++ b/apps/blog/content/blog/from-rust-to-typescript-a-new-chapter-for-prisma-orm/index.mdx @@ -34,7 +34,7 @@ In our [recently released ORM Manifesto](https://www.prisma.io/blog/prisma-orm-m This may have been only a sentence in our post, but it has caused quite a few reactions: - + For example we really loved this video from Theo: @@ -45,7 +45,7 @@ In short, we want to let everyone in the community know **what is changing, the ## Why did Prisma choose Rust? -Before we can explore the future of Prisma ORM, we need to understand why Prisma ORM uses a Rust engine. When we started planning Prisma 2 (now known as Prisma ORM), we had a pretty clear vision: we wanted to build ORMs for as many languages as possible—TypeScript, Go, Python, Scala, Rust, and others. We needed a solution that would make adding support for new languages relatively straightforward. Rust’s performance benefits and systems-level approach made it a natural choice for this core query engine. +Before we can explore the future of Prisma ORM, we need to understand why Prisma ORM uses a Rust engine. When we started planning Prisma 2 (now known as Prisma ORM), we had a pretty clear vision: we wanted to build ORMs for as many languages as possible—TypeScript, Go, Python, Scala, Rust, and others. We needed a solution that would make adding support for new languages relatively straightforward. Rust’s performance benefits and systems-level approach made it a natural choice for this core query engine. This decision was also a continuation of the work done on GraphCool and Prisma 1. The core, deployable infrastructure of these earlier solutions evolved into the Rust-based query engine—a binary designed to handle the heavy lifting of generating SQL queries, managing connection pools, and returning results from your database. This freed up language-specific clients like `prisma-client-js` to remain lightweight layers on top of the engine. @@ -71,8 +71,8 @@ To understand this change, let’s review the current query engine setup. Today, there are two ways that you can query a database with Prisma ORM: -- Using a database driver written in *Rust*. -- Using a [driver adapter](https://www.prisma.io/docs/orm/overview/databases/database-drivers#driver-adapters) and driver both written in *TypeScript*. +- Using a database driver written in _Rust_. +- Using a [driver adapter](https://www.prisma.io/docs/orm/overview/databases/database-drivers#driver-adapters) and driver both written in _TypeScript_. In the first approach, Prisma ORM queries are passed to the query engine, written in Rust. This engine manages everything from building the query plan to executing queries and returning results to the JavaScript client: @@ -113,7 +113,7 @@ For instance, we plan to remove the requirement for `binaryTargets`, further str ### Unlocking future opportunities -This transition isn’t just about addressing current challenges—it creates new opportunities for innovation. In fact, the query compiler enables many possibilities for our team and the community to explore. For example, the use of parameterized query plans could allow for **saving query plans for re-use** to speed up execution. Another avenue would be to build the initial query plans *at compile time*, further reducing runtime computation needs. +This transition isn’t just about addressing current challenges—it creates new opportunities for innovation. In fact, the query compiler enables many possibilities for our team and the community to explore. For example, the use of parameterized query plans could allow for **saving query plans for re-use** to speed up execution. Another avenue would be to build the initial query plans _at compile time_, further reducing runtime computation needs. We’re excited about these possibilities and eager to hear your thoughts! Join the discussion on our GitHub or Discord. @@ -133,7 +133,7 @@ Finally, test our Early Access client! We’ll share updates on GitHub and Disco This is an exciting time for Prisma, with even more improvements and opportunities ahead. Thank you for inspiring us to grow and for being part of this journey. -*Want to be among the first to try our new Early Access client? [Follow us on X](https://pris.ly/x) and [join our Discord](https://pris.ly/discord) to stay updated.* +_Want to be among the first to try our new Early Access client? [Follow us on X](https://pris.ly/x) and [join our Discord](https://pris.ly/discord) to stay updated._ ## Frequently Asked Questions (FAQ) diff --git a/apps/blog/content/blog/full-stack-typesafety-with-angular-nest-nx-and-prisma-CcMK7fbQfTWc/index.mdx b/apps/blog/content/blog/full-stack-typesafety-with-angular-nest-nx-and-prisma-CcMK7fbQfTWc/index.mdx index 1789674e56..8321d3a677 100644 --- a/apps/blog/content/blog/full-stack-typesafety-with-angular-nest-nx-and-prisma-CcMK7fbQfTWc/index.mdx +++ b/apps/blog/content/blog/full-stack-typesafety-with-angular-nest-nx-and-prisma-CcMK7fbQfTWc/index.mdx @@ -38,26 +38,20 @@ Let's start by creating an Nx workspace for our project. We'll use the `create-n In a terminal window, create a workspace with a preset of `angular`. - - ```shell npx create-nx-workspace --preset=angular ``` - An interactive prompt takes us through the setup process. Select a name for the workspace and application and then continue through the prompts. ![Interactive prompts for setting up an Nx workspace](/full-stack-typesafety-with-angular-nest-nx-and-prisma-CcMK7fbQfTWc/imgs/nx-prisma-1.png) Once Nx finishes wiring up the workspace, open it up and try running the Angular application. - - ```shell npm start ``` - This command will tell Nx to serve the Angular application that was created as the workspace initialized. After it compiles, open up `localhost:4200` to make sure everything is working. ![The Angular application running on localhost:4200](/full-stack-typesafety-with-angular-nest-nx-and-prisma-CcMK7fbQfTWc/imgs/nx-prisma-2.png) @@ -68,33 +62,24 @@ Our front end is ready to go but we haven't yet included a project for the backe To add our NestJS project, we first need to install the official NestJS plugin for Nx. In a new terminal window, grab the `@nrwl/nx` package from npm. - - ```shell npm install -D @nrwl/nest ``` - After installation, use the plugin to generate a NestJS project within the workspace. Since we'll only have one backend project for this example, let's just name it "api". - - ```shell nx generate @nrwl/nest:application api ``` - Once the generator finishes, we can see a new folder called `api` under the `apps` directory. This is where our NestJS app lives. The default NestJS installation comes with a single endpoint which returns a "hello world" message. Let's start the API and make sure we can access the endpoint. To start the API, target the `nx serve` command directly at the NestJS app. - - ```shell nx serve api ``` - Once the API is up and running, go to `http://localhost:3333/api` in the browser and make sure you can see the "hello world" message. ![The NestJS application running on localhost:3333./imgs/nx-prisma-3.png) @@ -105,25 +90,19 @@ Now that we've got our front end and backend projects in place, let's set up Pri We need to install two packages to work with Prisma: the Prisma Client (as a regular dependency) and the Prisma CLI (as a dev dependency). - - ```shell npm install @prisma/client npm install -D @prisma/cli ``` - The Prisma Client is what gives us ORM-style type-safe database access in our code. The Prisma CLI is what gives us a set of commands to initialize Prisma, create database migrations, and more. With those packages installed, let's initialize Prisma. - - ```shell npx prisma init ``` - After running this command, a `prisma` directory is created at the workspace root. Inside is a single file called `schema.prisma`. This file uses the [Prisma Schema Language](https://www.prisma.io/docs/concepts/components/prisma-schema) and is the place where we define the shape of our database. We use it to describe the tables for our databases and their columns, the relationships between tables, and more. @@ -132,8 +111,6 @@ When we create a Prisma model, we need to select a `provider` for our datasource Instead of using Postgres, let's use SQLite so we can keep things simple. Switch up the `db` datasource so that uses SQLite. Point the `url` parameter to a file called `dev.db` within the filesystem. - - ```prisma datasource db { provider = "sqlite" @@ -141,13 +118,10 @@ datasource db { } ``` - **Note:** We don't need to create the `dev.db` file ourselves. Its creation will be taken care of for us in a later step. Let's now set up a simple model for our shop. To get ourselves started, let's work with a single table called `Product`. To do so, create a new `model` in the schema file and give it some fields. - - ```prisma model Product { id String @id @default(cuid()) @@ -159,18 +133,14 @@ model Product { } ``` - The `id` field is marked as the primary key via the `@id` directive. We're also setting its default value to be a collision-resistant unique ID. The other fields and fairly straight-forward in their purpose. With the model in place, let's run our first migration so that the filesystem database file gets created and populated with our `Product` table. - - ```shell npx prisma migrate dev --preview-feature ``` - An interactive prompt will ask for the name of the migration. Call it whatever you like, something like `init` works fine. After the migration completes, a `dev.db` file is created in the `prisma` directory, along with a `migrations` directory. It's within the `migrations` directory that all of the SQL that's used to perform our database migrations is stored. Since these files are raw SQL, we have the opportunity to adjust them before they operate on our databases. Read the [migrate docs](https://www.prisma.io/docs/concepts/components/prisma-migrate) to find out more about how you can customize the migration behavior. @@ -181,13 +151,10 @@ With the database in place and populated with a table, we can now take a look at In a new terminal window, use the Prisma CLI to fire up Prisma Studio. - - ```shell npx prisma studio ``` - Running this command will open Prisma Studio. In the browser, it opens at `localhost:5555`. ![Prisma Studio running at localhost:5555](/full-stack-typesafety-with-angular-nest-nx-and-prisma-CcMK7fbQfTWc/imgs/nx-prisma-4.png) @@ -210,38 +177,32 @@ The data in our database is ready to go. What we need now is an endpoint we can Use the NestJS Nx plugin to generate a new library called `products`. Include a controller and a service within. - - ```shell nx generate @nrwl/nest:library products --controller --service ``` - We'll create a method in the service to reach into our database to get the data. Then, in the controller, we'll expose a `GET` endpoint which uses the service to get that data and return it to the client. Let's start by building out the database query within the service. This is the first spot we'll see Prisma's types really shine! Within `products.service.ts`, import `PrismaClient`, create an instance of it, and expose a `public` method to query for the data. - - ```ts // libs/products/src/lib/products.service.ts -import { Injectable } from '@nestjs/common' -import { PrismaClient, Product } from '@prisma/client' +import { Injectable } from "@nestjs/common"; +import { PrismaClient, Product } from "@prisma/client"; -const prisma = new PrismaClient() +const prisma = new PrismaClient(); @Injectable() export class ProductService { public getProducts(): Promise { - return prisma.product.findMany() + return prisma.product.findMany(); } } ``` - We're importing two things from `@prisma/client` here: `PrismaClient` and `Product`. `PrismaClient` is what we use to create an instance of our database client and it exposes methods and properties that are useful for querying the database. @@ -252,42 +213,37 @@ The `Product` import is the TypeScript type that was generated for us by Prisma Let's now work within the controller to make a call to `getProducts` to fetch the data. Open up `products.controller.ts` and add a method which responds to `GET` requests. - - ```ts // libs/products/src/lib/products.controller.ts -import { Controller, Get } from '@nestjs/common' -import { ProductsService } from './products.service' +import { Controller, Get } from "@nestjs/common"; +import { ProductsService } from "./products.service"; -@Controller('products') +@Controller("products") export class ProductController { constructor(private productService: ProductsService) {} @Get() public getProducts() { - return this.productService.getProducts() + return this.productService.getProducts(); } } ``` - We've applied the `getProducts` method with the `@Get` decorator which means when we make a `GET` request to `/products`, the method will be run. The method itself reaches into the service to get the data. Before we can test out this endpoint, we need to add `ProductsController` and `ProductsService` in the main module for the `api`. Open up `app.module.ts` found within `apps/api/src/app` and import `ProductsController` and `ProductsService`. Then include them in the `controllers` and `providers` arrays respectively. - - ```ts // apps/api/src/app/app.module.ts -import { Module } from '@nestjs/common' +import { Module } from "@nestjs/common"; -import { AppController } from './app.controller' -import { AppService } from './app.service' -import { ProductsController, ProductsService } from '@shirt-shop/products' +import { AppController } from "./app.controller"; +import { AppService } from "./app.service"; +import { ProductsController, ProductsService } from "@shirt-shop/products"; @Module({ imports: [], @@ -297,7 +253,6 @@ import { ProductsController, ProductsService } from '@shirt-shop/products' export class AppModule {} ``` - Now head over to the browser and test it out by going to `http://localhost:3333/api/products`. ![Products data from the API](/full-stack-typesafety-with-angular-nest-nx-and-prisma-CcMK7fbQfTWc/imgs/nx-prisma-7.png) @@ -316,78 +271,64 @@ Instead of setting up a proxy for this demo, we can instead enable CORS on the b Open up `apps/api/src/main.ts` and add a call to `app.enableCors(); - - ```ts // apps/api/src/main.ts -import { Logger } from '@nestjs/common' -import { NestFactory } from '@nestjs/core' +import { Logger } from "@nestjs/common"; +import { NestFactory } from "@nestjs/core"; -import { AppModule } from './app/app.module' +import { AppModule } from "./app/app.module"; async function bootstrap() { - const app = await NestFactory.create(AppModule) - const globalPrefix = 'api' - app.setGlobalPrefix(globalPrefix) - app.enableCors() - const port = process.env.PORT || 3333 + const app = await NestFactory.create(AppModule); + const globalPrefix = "api"; + app.setGlobalPrefix(globalPrefix); + app.enableCors(); + const port = process.env.PORT || 3333; await app.listen(port, () => { - Logger.log('Listening at http://localhost:' + port + '/' + globalPrefix) - }) + Logger.log("Listening at http://localhost:" + port + "/" + globalPrefix); + }); } -bootstrap() +bootstrap(); ``` - ## Create a UI Module for the Angular App We could just start building components directly within the `shirt-shop` app in our Nx workspace, but that would be against the advice that Nx gives about how to manage code in our monorepos. Instead, let's create a new module that will be dedicated to components that make up our UI. Head over to the command line and create a new module. Follow the prompts to select the desired CSS variety. - - ```shell nx generate @nrwl/angular:lib ui ``` - Once the module is in place, we can create a component to list our products as well as a service to make the API call to get the data. Let's start by generating a component. - - ```shell nx g component products --project=ui --export ``` - Using the `--project=ui` flag tells Nx that we want to put this component in our newly-created `ui` module. We can see the result under `/libs/ui/src/lib/products`. Let's now create a service. - - ```shell nx g service product --project=ui --export ``` - With the new `UiModule` in place, we now need to add it to the `imports` array in our `app.module.ts` file for the frontend. - - ```ts // apps/shirt-shop/src/app/app.module.ts -import { BrowserModule } from '@angular/platform-browser' -import { NgModule } from '@angular/core' +import { BrowserModule } from "@angular/platform-browser"; +import { NgModule } from "@angular/core"; -import { AppComponent } from './app.component' -import { UiModule } from '@shirt-shop/ui' +import { AppComponent } from "./app.component"; +import { UiModule } from "@shirt-shop/ui"; @NgModule({ declarations: [AppComponent], @@ -398,22 +339,19 @@ import { UiModule } from '@shirt-shop/ui' export class AppModule {} ``` - **Note:** If you get any errors saying that `@shirt-shop/ui` cannot be found, try restarting the front end by stopping that process and running `nx serve` again. ## Add an API Call to the Service We'll use Angular's built-in `HttpClientModule` to get access to an HTTP client for making requests to the API. To get started, let's import the appropriate module. The place to do this is within the `ui.module.ts` file in our new `UiModule`. - - ```ts // libs/ui/src/lib/ui.module.ts -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { ProductsComponent } from './products/products.component' -import { HttpClientModule } from '@angular/common/http' +import { NgModule } from "@angular/core"; +import { CommonModule } from "@angular/common"; +import { ProductsComponent } from "./products/products.component"; +import { HttpClientModule } from "@angular/common/http"; @NgModule({ imports: [CommonModule, HttpClientModule], @@ -423,36 +361,32 @@ import { HttpClientModule } from '@angular/common/http' export class UiModule {} ``` - We can now import Angular's `HttpClient` within our `ProductService` and make calls with it. - - ```ts // libs/ui/src/lib/product.service.ts -import { HttpClient } from '@angular/common/http' -import { Injectable } from '@angular/core' -import { Product } from '@prisma/client' -import { Observable } from 'rxjs' +import { HttpClient } from "@angular/common/http"; +import { Injectable } from "@angular/core"; +import { Product } from "@prisma/client"; +import { Observable } from "rxjs"; @Injectable({ - providedIn: 'root', + providedIn: "root", }) export class ProductService { - private API_URL: string = 'http://localhost:3333/api' + private API_URL: string = "http://localhost:3333/api"; constructor(private readonly http: HttpClient) {} public getProducts(): Observable { { - return this.http.get(`${this.API_URL}/products`) + return this.http.get(`${this.API_URL}/products`); } } } ``` - Notice that we're using the same `Product` type that gets exported from `@prisma/client` here within our `ProductService` that was used on the backend in the `ProductsController`. This is a great illustration of how we can benefit from using the same types across our whole stack. When we use the `getProducts` method from this service, we'll now have type safety applied. ## Build Out the Products Component @@ -463,7 +397,6 @@ Let's start by adding some CSS that will style our component. Open up `libs/ui/src/lib/products/product.component.css` and add the following styles: - ``` /* libs/ui/src/lib/products/product.component.css */ @@ -514,11 +447,8 @@ Open up `libs/ui/src/lib/products/product.component.css` and add the following s } ``` - Next, open up `libs/ui/src/lib/products/product.component.html` and add the structure for products to be displayed.. - - ```html
@@ -531,36 +461,32 @@ Next, open up `libs/ui/src/lib/products/product.component.html` and add the stru
``` - Finally, we need to add a method to the component class which uses the `ProductService` to get the data. We'll then put the result on the `$products` observable that we've already stubbed out in our template above. - - ```ts // libs/ui/src/lib/products/products.component.ts -import { Component, OnInit } from '@angular/core' -import { ProductService } from '../product.service' -import { Observable } from 'rxjs' -import { Product } from '@prisma/client' +import { Component, OnInit } from "@angular/core"; +import { ProductService } from "../product.service"; +import { Observable } from "rxjs"; +import { Product } from "@prisma/client"; @Component({ - selector: 'shirt-shop-products', - templateUrl: './products.component.html', - styleUrls: ['./products.component.css'], + selector: "shirt-shop-products", + templateUrl: "./products.component.html", + styleUrls: ["./products.component.css"], }) export class ProductsComponent implements OnInit { - public $products: Observable + public $products: Observable; constructor(public productService: ProductService) {} ngOnInit(): void { - this.$products = this.productService.getProducts() + this.$products = this.productService.getProducts(); } } ``` - This is another spot where we're using our `Product` type from `@prisma/client` to give ourselves type safety. Applying this type directly to the `$products` observable means that we can get autocompletion in our Angular templates. ![Autocompletion in the Angular template](/full-stack-typesafety-with-angular-nest-nx-and-prisma-CcMK7fbQfTWc/imgs/nx-prisma-9.png) @@ -569,14 +495,11 @@ With our component in place, we're now ready to call it from the `shirt-shop` ap Open up `apps/shirt-shop/src/app/app.component.html` and include the `Products` component. - - ```html

Welcome to Shirt Shop!

``` - ![Products from ShirtShop](/full-stack-typesafety-with-angular-nest-nx-and-prisma-CcMK7fbQfTWc/imgs/nx-prisma-10.png) ## Going Beyond Displaying Data @@ -587,13 +510,11 @@ We won't build out a full CRUD experience for this demonstration, but we can tak Let's say we have a section in our app which allows admins to add new products in. We'd likely want to start by creating an endpoint to receive this data and store it. In this case, we could use the `create` method on `PrismaClient` along with the `ProductCreateInput` type that is exposed on a top-level export called `Prisma`. - - ```ts -import { Injectable } from '@nestjs/common' -import { PrismaClient, Product, Prisma } from '@prisma/client' +import { Injectable } from "@nestjs/common"; +import { PrismaClient, Product, Prisma } from "@prisma/client"; -const prisma = new PrismaClient() +const prisma = new PrismaClient(); @Injectable() export class ProductService { @@ -602,12 +523,11 @@ export class ProductService { public createProduct(data: Prisma.ProductCreateInput): Promise { return prisma.product.create({ data, - }) + }); } } ``` - The `createProduct` method takes in some data which is type-hinted to abide by the `Product` model from our Prisma schema. The returned result is a single `Product` that gets resolved from a `Promise`. It should be noted that just type-hinting our `data` parameter here doesn't do anything to add real validation to this endpoint. For data validation at the endpoint, we need to use [Validation Pipes](https://docs.nestjs.com/techniques/validation) from NestJS. @@ -617,4 +537,3 @@ It should be noted that just type-hinting our `data` parameter here doesn't do a TypeScript has come a long way since its early days and early adoption in the Angular community. Using TypeScript on both the frontend and backend bodes well for developer experience and confidence. Applying type safety to database access goes one step further in providing teams large and small with a slew of benefits. Wrapping the whole application up in a monorepo like those provided by Nx gives us an easy way of reusing code (including type definitions) across the whole stack. If you'd like to go even further with Prisma, [check out the docs](https://www.prisma.io/docs), follow us on [X/Twitter](https://pris.ly/x), and join our [Slack community](https://slack.prisma.io/)! - diff --git a/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-2-fwpc6ds155/index.mdx b/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-2-fwpc6ds155/index.mdx index 13618bd665..784194b783 100644 --- a/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-2-fwpc6ds155/index.mdx +++ b/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-2-fwpc6ds155/index.mdx @@ -83,6 +83,7 @@ To get started, navigate into the directory of your choice and run the following ```shell git clone -b part-2 https://github.com/m-abdelwahab/awesome-links.git ``` + You can now navigate into the cloned directory, install the dependencies and start the development server: ```shell @@ -90,6 +91,7 @@ cd awesome-links npm install npm run dev ``` + The app will be running at [`http://localhost:3000/`](http://localhost:3000/) and you will see four items. The data is hardcoded and comes from the `/data/links.ts` file. ![What the starter project looks like](/fullstack-nextjs-graphql-prisma-2-fwpc6ds155/imgs/awesome-links-starter-project.png) @@ -101,16 +103,19 @@ After setting up a PostgreSQL database, rename the `env.example` file to `.env` ```shell npx prisma migrate dev --name init ``` + If `prisma migrate dev` did not trigger the seed step, run the following command to seed the database: ```shell npx prisma db seed ``` + This command will run the `seed.ts` script, located in the `/prisma` directory. This script adds four links and one user to your database using Prisma Client. ### A look at the project structure and dependencies You will see the following folder structure + ``` awesome-links/ ┣ components/ @@ -139,6 +144,7 @@ awesome-links/ ┣ tailwind.config.js ┗ tsconfig.json ``` + This is a Next.js application with TailwindCSS set up along with Prisma. In the `pages` directory, you will find three files: @@ -171,11 +177,11 @@ While REST APIs offer advantages, they also have some drawbacks. We will use `aw Here is one possible way of structuring the REST API of `awesome-links`: | Resource | HTTP Method | Route | Description | -| -------- | ---------------------- | ------------ | ---------------------------------------------------------------------- | +| -------- | ---------------------- | ------------ | ---------------------------------------------------------------------- | --- | | `User` | `GET` | `/users` | returns all users and their information | | `User` | `GET` | `/users/:id` | returns a single user | | `Link` | `GET` | `/links` | returns all links | -| `Link` | `GET`, `PUT`, `DELETE` | `/links/:id` | returns a single link, updates it or deletes it. `id` is the link's id | | +| `Link` | `GET`, `PUT`, `DELETE` | `/links/:id` | returns a single link, updates it or deletes it. `id` is the link's id | | | `User` | `GET` | `/favorites` | returns a user's bookmarked links | | `User` | `POST` | `/link/save` | adds a link to the user's favorites | | `Link` | `POST` | `/link/new` | creates a new link (done by admin) | @@ -231,6 +237,7 @@ query { } } ``` + ![Example of GraphQL query](/fullstack-nextjs-graphql-prisma-2-fwpc6ds155/imgs/graphiql-example-query.png) The API only returns the `id` and `title`, even though a link has more fields. @@ -267,6 +274,7 @@ enum Role { USER } ``` + The `User` type has the following fields: - `id`, which is of type `ID`. @@ -288,6 +296,7 @@ type Link { users: [User] } ``` + This is a many-to-many relation between the `Link` and `User` object types since a `Link` can have many users, and a `User` can have many links. This is modeled in the database using Prisma. ### Defining Queries @@ -302,6 +311,7 @@ type Query { links: [Link]! } ``` + The `links` query returns an array of type `Link`. The `!` is used to indicate that this field is non-nullable, meaning that the API will always return a value when this field is queried. You can add more queries depending on the type of API you want to build. For the "awesome-links" app, you can add a query to return a single link, another one to return a single user, and another to return all users. @@ -314,6 +324,7 @@ type Query { users: [User]! } ``` + - The `link` query takes an argument `id` of type `ID` and returns a `Link`. The `id` argument is required, and the response is non-nullable. - The `user` query takes an argument `id` of type `ID` and returns a `User`. The `id` argument is required, and the response is non-nullable. - The `users` query returns an array of type `User`. The `id` argument is required. The response is non-nullable. @@ -326,11 +337,25 @@ For the "awesome-links" app, you will need different mutations for creating, upd ```graphql type Mutation { - createLink(category: String!, description: String!, imageUrl: String!, title: String!, url: String!): Link! + createLink( + category: String! + description: String! + imageUrl: String! + title: String! + url: String! + ): Link! deleteLink(id: ID!): Link! - updateLink(category: String, description: String, id: String, imageUrl: String, title: String, url: String): Link! + updateLink( + category: String + description: String + id: String + imageUrl: String + title: String + url: String + ): Link! } ``` + - The `createLink` mutation takes as an argument a `category`, a `description`, a `title`, a `url` and an `imageUrl`. All of these fields are of type `String` and are required. This mutation returns a `Link` object type. - The `deleteLink` mutation takes as an `id` of type `ID` as a required argument. It returns a required `Link`. - The `updateLink` mutation takes the same arguments as the `createLink` mutation. However, arguments are optional. This way, when updating a `Link` you will only pass the fields you want to be updated. This mutation returns a required `Link`. @@ -352,6 +377,7 @@ To get started, in the starter repo you cloned in the beginning, run the followi ```shell npm install graphql graphql-yoga ``` + The `graphql` package is the JavaScript reference implementation for GraphQL. It is a peer-dependency for `graphql-yoga`. ### Defining the schema of the app @@ -375,8 +401,9 @@ export const typeDefs = ` type Query { links: [Link]! } -` +`; ``` + ### Defining resolvers The next thing you need to do is create the resolver function for the `links` query. To do so, create a `/graphql/resolvers.ts` file and add the following code: @@ -388,34 +415,35 @@ export const resolvers = { links: () => { return [ { - category: 'Open Source', - description: 'Fullstack React framework', + category: "Open Source", + description: "Fullstack React framework", id: 1, - imageUrl: 'https://nextjs.org/static/twitter-cards/home.png', - title: 'Next.js', - url: 'https://nextjs.org', + imageUrl: "https://nextjs.org/static/twitter-cards/home.png", + title: "Next.js", + url: "https://nextjs.org", }, { - category: 'Open Source', - description: 'Next Generation ORM for TypeScript and JavaScript', + category: "Open Source", + description: "Next Generation ORM for TypeScript and JavaScript", id: 2, - imageUrl: 'https://www.prisma.io/images/og-image.png', - title: 'Prisma', - url: 'https://www.prisma.io', + imageUrl: "https://www.prisma.io/images/og-image.png", + title: "Prisma", + url: "https://www.prisma.io", }, { - category: 'Open Source', - description: 'GraphQL implementation', + category: "Open Source", + description: "GraphQL implementation", id: 3, - imageUrl: 'https://www.apollographql.com/apollo-home.png', - title: 'Apollo GraphQL', - url: 'https://apollographql.com', + imageUrl: "https://www.apollographql.com/apollo-home.png", + title: "Apollo GraphQL", + url: "https://apollographql.com", }, - ] + ]; }, }, -} +}; ``` + `resolvers` is an object where you will define the implementation for each query and mutation. The functions inside the `Query` object must match the names of the queries defined in the schema. Same thing goes for mutations. Here the `links` resolver function returns an array of objects, where each object is of type `Link`. @@ -428,29 +456,29 @@ Go ahead and create a `/pages/api/graphql.ts` file and add the following code: ```ts // pages/api/graphql.ts -import { createSchema, createYoga } from 'graphql-yoga' -import type { NextApiRequest, NextApiResponse } from 'next' -import { resolvers } from '../../graphql/resolvers' -import { typeDefs } from '../../graphql/schema' - +import { createSchema, createYoga } from "graphql-yoga"; +import type { NextApiRequest, NextApiResponse } from "next"; +import { resolvers } from "../../graphql/resolvers"; +import { typeDefs } from "../../graphql/schema"; export default createYoga<{ - req: NextApiRequest - res: NextApiResponse + req: NextApiRequest; + res: NextApiResponse; }>({ schema: createSchema({ typeDefs, - resolvers + resolvers, }), - graphqlEndpoint: '/api/graphql' -}) + graphqlEndpoint: "/api/graphql", +}); export const config = { api: { - bodyParser: false - } -} + bodyParser: false, + }, +}; ``` + You created a new GraphQL Yoga server instance that is the default export. You also created a schema using the `createSchema` function that takes the type-definitions and resolvers as a parameter. You then specified the path for the GraphQL API with the `graphqlEndpoint` property to `/api/graphql`. @@ -464,6 +492,7 @@ After completing the previous steps, start the server by running the following c ```shell npm run dev ``` + When you navigate to [`http://localhost:3000/api/graphql/`](http://localhost:3000/api/graphql/), you should see the following page: ![GraphiQL Playground for running queries](/fullstack-nextjs-graphql-prisma-2-fwpc6ds155/imgs/graphiql-playground.png) @@ -481,6 +510,7 @@ query { } } ``` + ![GraphiQL Playground with an example query](/fullstack-nextjs-graphql-prisma-2-fwpc6ds155/imgs/graphiql-documentation-explorer.png) The responses should be visible on the left panel, similar to the screenshot above. @@ -497,24 +527,25 @@ Prisma Client is an auto-generated, type-safe, query builder. To be able to use ```ts // /lib/prisma.ts -import { PrismaClient } from '@prisma/client' +import { PrismaClient } from "@prisma/client"; -let prisma: PrismaClient +let prisma: PrismaClient; declare global { var prisma: PrismaClient; } -if (process.env.NODE_ENV === 'production') { - prisma = new PrismaClient() +if (process.env.NODE_ENV === "production") { + prisma = new PrismaClient(); } else { if (!global.prisma) { - global.prisma = new PrismaClient() + global.prisma = new PrismaClient(); } - prisma = global.prisma + prisma = global.prisma; } -export default prisma +export default prisma; ``` + First, you are creating a new Prisma Client instance. Then if you are not in a production environment, Prisma will be attached to the global object so that you do not exhaust the database connection limit. For more details, check out the documentation for [Next.js and Prisma Client best practices](https://www.prisma.io/docs/guides/other/troubleshooting-orm/help-articles/nextjs-prisma-client-dev-practices). ### Query the database using Prisma @@ -523,15 +554,16 @@ Now you can update the resolver to return data from the database. Inside the `/g ```ts // /graphql/resolvers.ts -import prisma from '../lib/prisma' +import prisma from "../lib/prisma"; export const resolvers = { Query: { links: () => { - return prisma.link.findMany() + return prisma.link.findMany(); }, }, -} +}; ``` + If everything is set up correctly, when you go to GraphiQL,at [`http://localhost:3000/api/graphql`](http://localhost:3000/api/graphql) and re-run the links query, the data should be retrieved from your database. ## The flaws with our current GraphQL setup @@ -562,6 +594,7 @@ To get started, run the following command to install Pothos and the Prisma plugi ```shell npm install @pothos/plugin-prisma @pothos/core ``` + Next, add the `pothos` generator block to your Prisma schema right below the `client` generator: ```prisma @@ -575,11 +608,13 @@ generator client { + provider = "prisma-pothos-types" +} ``` + Run the following command to re-generate Prisma Client and Pothos types: ```sh npx prisma generate ``` + Next, create an instance of the Pothos schema builder as a shareable module. Inside the `graphql` folder, create a new file called `builder.ts` and add the following snippet: ```ts @@ -587,23 +622,23 @@ Next, create an instance of the Pothos schema builder as a shareable module. Ins // 1. import SchemaBuilder from "@pothos/core"; -import PrismaPlugin from '@pothos/plugin-prisma'; -import type PrismaTypes from '@pothos/plugin-prisma/generated'; +import PrismaPlugin from "@pothos/plugin-prisma"; +import type PrismaTypes from "@pothos/plugin-prisma/generated"; import prisma from "../lib/prisma"; -// 2. +// 2. export const builder = new SchemaBuilder<{ - // 3. - PrismaTypes: PrismaTypes + // 3. + PrismaTypes: PrismaTypes; }>({ // 4. plugins: [PrismaPlugin], prisma: { client: prisma, - } -}) + }, +}); -// 5. +// 5. builder.queryType({ fields: (t) => ({ ok: t.boolean({ @@ -612,6 +647,7 @@ builder.queryType({ }), }); ``` + 1. Defines all the libraries and utilities that will be needed 1. Creates a new `SchemaBuilder` instance 1. Defines the static types that will be used in creating the GraphQL schema @@ -625,8 +661,9 @@ Next, in the `/graphql/schema.ts` file replace the `typeDefs` with the following import { builder } from "./builder"; -export const schema = builder.toSchema() +export const schema = builder.toSchema(); ``` + Finally, update the import in the `/pages/api/graphql.ts` file: ```ts @@ -657,6 +694,7 @@ export const config = { } } ``` + ```ts @@ -694,24 +732,25 @@ The first step is defining a `Link` object type using Pothos. Go ahead and creat // /graphql/types/Link.ts import { builder } from "../builder"; -builder.prismaObject('Link', { +builder.prismaObject("Link", { fields: (t) => ({ - id: t.exposeID('id'), - title: t.exposeString('title'), - url: t.exposeString('url'), - description: t.exposeString('description'), - imageUrl: t.exposeString('imageUrl'), - category: t.exposeString('category'), - users: t.relation('users') - }) -}) + id: t.exposeID("id"), + title: t.exposeString("title"), + url: t.exposeString("url"), + description: t.exposeString("description"), + imageUrl: t.exposeString("imageUrl"), + category: t.exposeString("category"), + users: t.relation("users"), + }), +}); ``` + Since you're using the Pothos' Prisma plugin, the `builder` instance provides utility methods for defining your GraphQL schema such as [`prismaObject`](https://pothos-graphql.dev/docs/plugins/prisma#creating-types-with-builderprismaobject). -`prismaObject` accepts two arguments: +`prismaObject` accepts two arguments: - `name`: The name of the model in the Prisma schema you would like to _expose_. -- `options`: The options for defining the type you're exposing such as the description, fields, etc. +- `options`: The options for defining the type you're exposing such as the description, fields, etc. > **Note**: You can use CTRL + Space to invoke your editor's intellisense and view the available arguments. @@ -725,20 +764,21 @@ Now create a new `/graphql/types/User.ts` file and add the following to code to // /graphql/types/User.ts import { builder } from "../builder"; -builder.prismaObject('User', { +builder.prismaObject("User", { fields: (t) => ({ - id: t.exposeID('id'), - email: t.exposeString('email', { nullable: true, }), - image: t.exposeString('image', { nullable: true, }), - role: t.expose('role', { type: Role, }), - bookmarks: t.relation('bookmarks'), - }) -}) + id: t.exposeID("id"), + email: t.exposeString("email", { nullable: true }), + image: t.exposeString("image", { nullable: true }), + role: t.expose("role", { type: Role }), + bookmarks: t.relation("bookmarks"), + }), +}); -const Role = builder.enumType('Role', { - values: ['USER', 'ADMIN'] as const, -}) +const Role = builder.enumType("Role", { + values: ["USER", "ADMIN"] as const, +}); ``` + Since the `email` and `image` fields in the Prisma schema are nullable, pass the `nullable: true` as a second argument to the expose method. The default type for the `role` field when "exposing" it's type from the generated schema. In the above example, you've defined an explicit enum type called `Role` which is then used to resolve the `role`'s field type. @@ -754,6 +794,7 @@ import { builder } from "./builder"; export const schema = builder.toSchema() ``` + ### Defining queries using Pothos In the `graphql/types/Link.ts` file, add the following code below the `Link` object type definition: @@ -762,23 +803,24 @@ In the `graphql/types/Link.ts` file, add the following code below the `Link` obj // graphql/types/Link.ts // code above unchanged -// 1. +// 1. builder.queryField("links", (t) => -// 2. + // 2. t.prismaField({ - // 3. - type: ['Link'], - // 4. - resolve: (query, _parent, _args, _ctx, _info) => - prisma.link.findMany({ ...query }) - }) -) + // 3. + type: ["Link"], + // 4. + resolve: (query, _parent, _args, _ctx, _info) => prisma.link.findMany({ ...query }), + }), +); ``` + In the above snippet: + 1. Defines a query type called `links`. 1. Defines the field that will resolve to the generated Prisma Client types. 1. Specifies the field that Pothos will use to resolve the field. In this case, it resolves to an array of the `Link` type -1. Defines the logic for the query. +1. Defines the logic for the query. The `query` argument in the resolver function adds a `select` or `include` to your query to resolve as many relation fields as possible in a single request. @@ -801,19 +843,21 @@ To get started with Apollo Client, add to your project by running the following ```shell npm install @apollo/client ``` + Next, in the `/lib` directory create a new file called `apollo.ts` and add the following code to it: ```ts // /lib/apollo.ts -import { ApolloClient, InMemoryCache } from '@apollo/client' +import { ApolloClient, InMemoryCache } from "@apollo/client"; const apolloClient = new ApolloClient({ - uri: '/api/graphql', + uri: "/api/graphql", cache: new InMemoryCache(), -}) +}); -export default apolloClient +export default apolloClient; ``` + You are creating a new `ApolloClient` instance to which you are passing a configuration object with `uri` and `cache` fields. - The `uri` field specifies the GraphQL endpoint you will interact with. This will be changed to the production URL when the app is deployed. @@ -841,6 +885,7 @@ function MyApp({ Component, pageProps }: AppProps) { export default MyApp ``` + You are wrapping the global `App` component with the Apollo Provider so all of the project's components can send GraphQL queries. > **Note**: Next.js supports different data fetching strategies. You can fetch data server-side, client-side, or at build-time. To support pagination, you need to fetch data client-side. @@ -851,9 +896,9 @@ To load data on your frontend using Apollo client, update the `/pages/index.tsx` ```tsx // /pages/index.tsx -import Head from 'next/head' -import { gql, useQuery } from '@apollo/client' -import type { Link } from '@prisma/client' +import Head from "next/head"; +import { gql, useQuery } from "@apollo/client"; +import type { Link } from "@prisma/client"; const AllLinksQuery = gql` query { @@ -866,13 +911,13 @@ const AllLinksQuery = gql` category } } -` +`; export default function Home() { - const { data, loading, error } = useQuery(AllLinksQuery) + const { data, loading, error } = useQuery(AllLinksQuery); - if (loading) return

Loading...

- if (error) return

Oh no... {error.message}

+ if (loading) return

Loading...

; + if (error) return

Oh no... {error.message}

; return (
@@ -891,7 +936,7 @@ export default function Home() {

{link.title}

{link.description}

- {link.url.replace(/(^\w+:|^)\/\//, '')} + {link.url.replace(/(^\w+:|^)\/\//, "")}
- ) + ); } ``` + You are using the `useQuery` hook to send queries to the GraphQL endpoint. This hook has a required parameter of a GraphQL query string. When the component renders, `useQuery` returns an object which contains three values: - `loading`: a boolean that determines whether or not the data has been returned. @@ -973,6 +1019,7 @@ query allLinksQuery($first: Int, $after: ID) { } } ``` + The query takes two arguments, `first` and `after`: - `first`: an `Int` that specifies how many items you want the API to return. @@ -1006,6 +1053,7 @@ Install the plugin with the following command: ```sh npm install @pothos/plugin-relay ``` + Update the `graphql/builder.ts` to include the relay plugin. ```ts @@ -1037,6 +1085,7 @@ builder.queryType({ }), }); ``` + ### Updating the resolver to return paginated data from the database To use cursor-based pagination make the following update to the `links` query: @@ -1057,7 +1106,8 @@ builder.queryField('links', (t) => }) ) ``` -The `prismaConnection` method is used to create a `connection` field that also pre-loads the data inside that connection. + +The `prismaConnection` method is used to create a `connection` field that also pre-loads the data inside that connection. @@ -1066,27 +1116,27 @@ The `prismaConnection` method is used to create a `connection` field that also p import { builder } from "../builder"; builder.prismaObject('Link', { - fields: (t) => ({ - id: t.exposeID('id'), - title: t.exposeString('title'), - url: t.exposeString('url'), - description: t.exposeString('description'), - imageUrl: t.exposeString('imageUrl'), - category: t.exposeString('category'), - users: t.relation('users') - }), +fields: (t) => ({ +id: t.exposeID('id'), +title: t.exposeString('title'), +url: t.exposeString('url'), +description: t.exposeString('description'), +imageUrl: t.exposeString('imageUrl'), +category: t.exposeString('category'), +users: t.relation('users') +}), }) - builder.queryField('links', (t) => - t.prismaConnection({ - type: 'Link', - cursor: 'id', - resolve: (query, _parent, _args, _ctx, _info) => - prisma.link.findMany({ ...query }) - }) +t.prismaConnection({ +type: 'Link', +cursor: 'id', +resolve: (query, \_parent, \_args, \_ctx, \_info) => +prisma.link.findMany({ ...query }) +}) ) -``` + +```` @@ -1098,7 +1148,7 @@ Here is a diagram that summarizes how pagination works on the server: Now that the API supports pagination, you can fetch paginated data on the client using Apollo Client. -The `useQuery` hook returns an object containing `data`, `loading` and `errors`. However, `useQuery` also returns a `fetchMore()` function, which is used to handle pagination and updating the UI when a result is returned. +The `useQuery` hook returns an object containing `data`, `loading` and `errors`. However, `useQuery` also returns a `fetchMore()` function, which is used to handle pagination and updating the UI when a result is returned. Navigate to the `/pages/index.tsx` file and update it to use the following code to add support for pagination: ```tsx @@ -1189,7 +1239,8 @@ function Home() { } export default Home; -``` +```` + You are first passing a `variables` object to the `useQuery` hook, which contains a key called `first` with a value of `2`. This means you will be fetching two links. You can set this value to any number you want. The `data` variable will contain the data returned from the initial request to the API. @@ -1223,4 +1274,3 @@ In [the next part](/fullstack-nextjs-graphql-prisma-3-clxbrcqppv) of the course, - Create an admin-only route for creating links - Set up AWS S3 to handle file uploads - Add a mutation to create links as an admin - diff --git a/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-3-clxbrcqppv/index.mdx b/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-3-clxbrcqppv/index.mdx index eeb9e078c4..9b211a8b77 100644 --- a/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-3-clxbrcqppv/index.mdx +++ b/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-3-clxbrcqppv/index.mdx @@ -18,7 +18,7 @@ tags: --- This article is the third part of the course where you build a fullstack app with Next.js, GraphQL, TypeScript, Prisma - and PostgreSQL. In this article, you will learn how to add authentication to your app. +and PostgreSQL. In this article, you will learn how to add authentication to your app. ## Table of Contents @@ -63,12 +63,14 @@ To get started, navigate into the directory of your choice and run the following ```shell git clone -b part-3 https://github.com/prisma/awesome-links.git ``` + Navigate into the cloned application and install the dependencies: ```shell cd awesome-links npm install ``` + ## Seed the database After setting up a PostgreSQL database, rename the `env.example` file to `.env` and set the connection string for your database. After that, run the following command to create the tables in your database: @@ -78,11 +80,13 @@ After setting up a PostgreSQL database, rename the `env.example` file to `.env` ```shell npx prisma migrate dev --name init ``` + If `prisma migrate dev` did not trigger the seed step, run the following command to seed the database: ```shell npx prisma db seed ``` + This command will run the `seed.ts` file in the `/prisma` directory. `seed.ts` creates four links and one user in your database using Prisma Client. You can now start the application server by running the following command: @@ -90,6 +94,7 @@ You can now start the application server by running the following command: ```shell npm run dev ``` + ### Project structure and dependencies The project has the following folder structure: @@ -131,6 +136,7 @@ awesome-links/ ┣ tailwind.config.js ┗ tsconfig.json ``` + This is a Next.js application that uses the following libraries and tools: - [Prisma](https://www.prisma.io) for database access/CRUD operations @@ -143,7 +149,7 @@ This is a Next.js application that uses the following libraries and tools: The `pages` directory contains the following files: - `index.tsx`: fetches links from the API and displays them on the page. The results are paginated and you can fetch more links. -- `_app.tsx`: root component that allows you to persist layouts and state when navigating between pages. +- `_app.tsx`: root component that allows you to persist layouts and state when navigating between pages. - `/api/graphql.ts`: GraphQL endpoint using Next.js's [API routes](https://nextjs.org/docs/pages/building-your-application/routing/api-routes). ## Authentication and securing the GraphQL API using Auth0 @@ -176,6 +182,7 @@ AUTH0_ISSUER_BASE_URL='https://YOUR_APP_DOMAIN' AUTH0_CLIENT_ID='YOUR_CLIENT_ID' AUTH0_CLIENT_SECRET='YOUR_CLIENT_SECRET' ``` + - `AUTH0_SECRET`: A long secret value used to encrypt the session cookie. You can generate a suitable string by running `openssl rand -hex 32` in your terminal. - `AUTH0_BASE_URL`: The base URL of your application. - `AUTH0_ISSUER_BASE_URL`: The URL of your Auth0 tenant domain. @@ -197,14 +204,16 @@ You can add Auth0 to your project by installing the [Auth0 Next.js SDK](https:// ```shell npm install @auth0/nextjs-auth0 ``` + Next, create an `auth/[...auth0].ts` file inside the `pages/api` directory and add the following code to it: ```ts // pages/api/auth/[...auth0].ts -import { handleAuth } from '@auth0/nextjs-auth0' +import { handleAuth } from "@auth0/nextjs-auth0"; -export default handleAuth() +export default handleAuth(); ``` + This [Next.js dynamic API route](https://nextjs.org/docs/routing/dynamic-routes#catch-all-routes) will automatically create the following endpoints: - `/api/auth/login`: Auth0's login route. @@ -216,12 +225,12 @@ Finally, navigate to the `pages/_app.tsx` file and update it with the following ```tsx // pages/_app.tsx -import '../styles/tailwind.css' -import { UserProvider } from '@auth0/nextjs-auth0/client' -import Layout from '../components/Layout' -import { ApolloProvider } from '@apollo/client' -import type { AppProps } from 'next/app' -import apolloClient from '../lib/apollo' +import "../styles/tailwind.css"; +import { UserProvider } from "@auth0/nextjs-auth0/client"; +import Layout from "../components/Layout"; +import { ApolloProvider } from "@apollo/client"; +import type { AppProps } from "next/app"; +import apolloClient from "../lib/apollo"; function MyApp({ Component, pageProps }: AppProps) { return ( @@ -232,11 +241,12 @@ function MyApp({ Component, pageProps }: AppProps) { - ) + ); } -export default MyApp +export default MyApp; ``` + Wrapping the `MyApp` component with the `UserProvider` component will allow all pages to access your user's authentication state. ### Secure the GraphQL API @@ -247,25 +257,25 @@ Create a `graphql/context.ts` file and add the following snippet: ```ts // graphql/context.ts -import { getSession } from '@auth0/nextjs-auth0' -import type { NextApiRequest, NextApiResponse } from 'next' +import { getSession } from "@auth0/nextjs-auth0"; +import type { NextApiRequest, NextApiResponse } from "next"; -export async function createContext({ req, res }: { req: NextApiRequest, res: NextApiResponse }) { - const session = await getSession(req, res) +export async function createContext({ req, res }: { req: NextApiRequest; res: NextApiResponse }) { + const session = await getSession(req, res); // if the user is not logged in, return an empty object - if (!session || typeof session === 'undefined') return {} + if (!session || typeof session === "undefined") return {}; - const { user, accessToken } = session + const { user, accessToken } = session; return { user, accessToken, - } + }; } ``` -The `getSession()` function from Auth0 returns information about the logged-in user and the access token. This data is then included in the GraphQL context. Your queries and mutations can now access the authentication state. +The `getSession()` function from Auth0 returns information about the logged-in user and the access token. This data is then included in the GraphQL context. Your queries and mutations can now access the authentication state. Update the server instance with the `context` property with the `createContext` function as it's value: @@ -294,6 +304,7 @@ export const config = { } } ``` + Next, update the `SchemaBuilder` function in `graphql/builder.ts` by specifying the type for the `Context` object: ```ts @@ -325,20 +336,24 @@ builder.queryType({ }), }); ``` + Finally, the app's navbar should display a **Login**/**Logout** button depending on the user's authentication state. Update the `Header` component in `components/Layout/Header.tsx` with the following code: ```tsx // components/Layout/Header.tsx -import React from 'react' -import Link from 'next/link' -import { useUser } from '@auth0/nextjs-auth0/client' +import React from "react"; +import Link from "next/link"; +import { useUser } from "@auth0/nextjs-auth0/client"; const Header = () => { - const { user } = useUser() + const { user } = useUser(); return (
- + {
- + Logout - profile + profile
) : ( - + Login )}
- ) -} + ); +}; -export default Header +export default Header; ``` + The `useUser` hook from Auth0 checks whether a user is authenticated or not. This hook runs client-side. If you have done all the previous steps correctly, you should be able to sign up and login to the app! @@ -389,11 +415,10 @@ Auth0 only manages users on your behalf and doesn't allow storing any data excep To achieve that, you will leverage [Auth0 Actions](https://auth0.com/docs/actions). Auth0 Actions are serverless functions that can execute at certain points during the Auth0 runtime. -You will define an API route that will receive the information sent from the Auth0 Action during the login process and save the information to your database. This pattern of creating an API endpoint to listen to events from a third party service is called a [webhook](https://sendgrid.com/blog/whats-webhook/). +You will define an API route that will receive the information sent from the Auth0 Action during the login process and save the information to your database. This pattern of creating an API endpoint to listen to events from a third party service is called a [webhook](https://sendgrid.com/blog/whats-webhook/). To get started with Auth0 Actions, navigate to the **Actions** dropdown located in the left sidebar, select **Flows** and choose **Login**. - ![Auth0 Actions choose flow](/fullstack-nextjs-graphql-prisma-3-clxbrcqppv/imgs/auth0-actions-login.png) Next, to create a new Action, click the **+** icon and choose **Build custom**. @@ -416,17 +441,17 @@ Here is a breakdown of the Auth0 Actions UI: The first step is to include the `node-fetch` module version `2.6.1`. You will use it in your Action to send a request to an API endpoint – you will create this later. This endpoint will handle the logic of creating a user record in the database. - ![Include package in Auth0 Action](/fullstack-nextjs-graphql-prisma-3-clxbrcqppv/imgs/auth0-action-add-module.png) -Next, define a secret that will be included in every request sent by the Action to your endpoint. This secret will ensure that the requests are coming from the Auth0 Action instead of another untrusted third party. +Next, define a secret that will be included in every request sent by the Action to your endpoint. This secret will ensure that the requests are coming from the Auth0 Action instead of another untrusted third party. You can generate a random secret using the following command in your terminal: ```shell openssl rand -hex 32 ``` -First, store this secret in the Auth0 dashboard with the key `AUTH0_HOOK_SECRET`. + +First, store this secret in the Auth0 dashboard with the key `AUTH0_HOOK_SECRET`. ![Auth0 add environment variables](/fullstack-nextjs-graphql-prisma-3-clxbrcqppv/imgs/auth0-action-add-hook-secret.png) @@ -435,46 +460,49 @@ Now, also store the secret in your `.env` file. ```shell AUTH0_HOOK_SECRET='' # same secret goes here ``` + ![Auth0 add environment variables](/fullstack-nextjs-graphql-prisma-3-clxbrcqppv/imgs/auth0-action-add-hook-secret.png) Finally, update the Action with the following code: ```js -const fetch = require('node-fetch') +const fetch = require("node-fetch"); exports.onExecutePostLogin = async (event, api) => { - // 1. - const SECRET = event.secrets.AUTH0_HOOK_SECRET - + // 1. + const SECRET = event.secrets.AUTH0_HOOK_SECRET; + // 2. if (event.user.app_metadata.localUserCreated) { - return + return; } // 3. - const email = event.user.email + const email = event.user.email; // 4. - const request = await fetch('http://localhost:3000/api/auth/hook', { // "localhost:3000" will be replaced before deploying this Action - method: 'post', + const request = await fetch("http://localhost:3000/api/auth/hook", { + // "localhost:3000" will be replaced before deploying this Action + method: "post", body: JSON.stringify({ email, secret: SECRET }), - headers: { 'Content-Type': 'application/json' }, - }) - const response = await request.json() + headers: { "Content-Type": "application/json" }, + }); + const response = await request.json(); // 5. - api.user.setAppMetadata('localUserCreated', true) -} + api.user.setAppMetadata("localUserCreated", true); +}; ``` + 1. Retrieves the `AUTH0_HOOK_SECRET` environment variable 1. Checks if the `localUserCreated` property on the user's `app_metadata` 1. Retrieves user's email from the login event – provided by Auth0 1. Sends a `POST` request to an API route – `http://localhost:3000/api/auth/hook` 1. Adds the `localUserCreated` property to the user's `app_metadata` -The `api.user.setAppMetadata` function allows you to add additional properties to a user's profile. +The `api.user.setAppMetadata` function allows you to add additional properties to a user's profile. -Before you deploy this action, there's one more thing left to do. +Before you deploy this action, there's one more thing left to do. ### Expose `localhost:3000` using Ngrok @@ -487,8 +515,9 @@ TODO: sign up for an account, get token from the dashboard While your app is running, run the following command to expose `localhost:3000`: ```shell -npx ngrok http 3000 --authtoken "TOKEN" +npx ngrok http 3000 --authtoken "TOKEN" ``` + > **Note**: Make sure to replace the `TOKEN` value with the token from Ngrok's dashboard. The output on your terminal will resemble the following – but with different **Forwarding** URLs: @@ -497,7 +526,7 @@ The output on your terminal will resemble the following – but with different * Copy the **Forwarding** URL, replace `localhost:3000` with your **Forwarding** URL in your Action and click **Deploy**. -Now that the action is deployed, go back to the **Login** flow by pressing the **Back to flow** button. +Now that the action is deployed, go back to the **Login** flow by pressing the **Back to flow** button. The final thing you need to do is add your newly created action to the **Login** flow. You will find the action underneath the **Custom** tab. To add the action to your flow, you can drag-and-drop it between **Start** and **Complete**. Then click **Apply** to save the changes. @@ -509,14 +538,14 @@ Create a `hook.ts` file in the `pages/api/auth/` folder and add the following co ```ts // pages/api/auth/hook.ts -import prisma from '../../../lib/prisma'; -import type { NextApiRequest, NextApiResponse } from 'next'; +import prisma from "../../../lib/prisma"; +import type { NextApiRequest, NextApiResponse } from "next"; const handler = async (req: NextApiRequest, res: NextApiResponse) => { const { email, secret } = req.body; // 1 - if (req.method !== 'POST') { - return res.status(403).json({ message: 'Method not allowed' }); + if (req.method !== "POST") { + return res.status(403).json({ message: "Method not allowed" }); } // 2 if (secret !== process.env.AUTH0_HOOK_SECRET) { @@ -536,6 +565,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { export default handler; ``` + This endpoint does the following: 1. Validates the request is a `POST` request @@ -552,7 +582,7 @@ Once a user signs up to your application, the user's information will be synced Navigate to `graphql/builder.ts` file and update with the following snippet: ```ts -diff +diff; // graphql/builder.ts // ...code above remains unchanged @@ -564,8 +594,9 @@ builder.queryType({ }), }); -+builder.mutationType({}) ++builder.mutationType({}); ``` + The above snippet registeres the `Mutation` type in the schema which allows you to define mutations in your GraphQL server. Next, update `graphql/types/Link.ts` with the following mutation that adds the ability to create links: @@ -576,7 +607,7 @@ Next, update `graphql/types/Link.ts` with the following mutation that adds the a builder.mutationField("createLink", (t) => t.prismaField({ - type: 'Link', + type: "Link", args: { title: t.arg.string({ required: true }), description: t.arg.string({ required: true }), @@ -585,10 +616,10 @@ builder.mutationField("createLink", (t) => category: t.arg.string({ required: true }), }, resolve: async (query, _parent, args, ctx) => { - const { title, description, url, imageUrl, category } = args + const { title, description, url, imageUrl, category } = args; if (!(await ctx).user) { - throw new Error("You have to be logged in to perform this action") + throw new Error("You have to be logged in to perform this action"); } return prisma.link.create({ @@ -599,30 +630,32 @@ builder.mutationField("createLink", (t) => url, imageUrl, category, - } - }) - } - }) -) + }, + }); + }, + }), +); ``` -The `args` property defines the input required to create a new link. The mutation also checks if a user is logged in so only authenticated users can create links. Finally, the `create()` function from Prisma creates a new database record. + +The `args` property defines the input required to create a new link. The mutation also checks if a user is logged in so only authenticated users can create links. Finally, the `create()` function from Prisma creates a new database record. Install the following dependencies you'll use for form management and notifications: ```sh npm install react-hook-form react-hot-toast ``` + Next, create `pages/admin.tsx` page and add the following code. The code allows creation of a new link: ```tsx // pages/admin.tsx -import React from 'react' -import { type SubmitHandler, useForm } from 'react-hook-form' -import { gql, useMutation } from '@apollo/client' -import toast, { Toaster } from 'react-hot-toast' -import { getSession } from '@auth0/nextjs-auth0' -import prisma from '../lib/prisma' -import type { GetServerSideProps } from 'next' +import React from "react"; +import { type SubmitHandler, useForm } from "react-hook-form"; +import { gql, useMutation } from "@apollo/client"; +import toast, { Toaster } from "react-hot-toast"; +import { getSession } from "@auth0/nextjs-auth0"; +import prisma from "../lib/prisma"; +import type { GetServerSideProps } from "next"; type FormValues = { title: string; @@ -630,11 +663,23 @@ type FormValues = { category: string; description: string; image: FileList; -} +}; const CreateLinkMutation = gql` - mutation createLink($title: String!, $url: String!, $imageUrl: String!, $category: String!, $description: String!) { - createLink(title: $title, url: $url, imageUrl: $imageUrl, category: $category, description: $description) { + mutation createLink( + $title: String! + $url: String! + $imageUrl: String! + $category: String! + $description: String! + ) { + createLink( + title: $title + url: $url + imageUrl: $imageUrl + category: $category + description: $description + ) { title url imageUrl @@ -642,7 +687,7 @@ const CreateLinkMutation = gql` description } } -` +`; const Admin = () => { const { @@ -650,38 +695,40 @@ const Admin = () => { handleSubmit, formState: { errors }, reset, - } = useForm() + } = useForm(); const [createLink, { loading, error }] = useMutation(CreateLinkMutation, { - onCompleted: () => reset() - }) + onCompleted: () => reset(), + }); const onSubmit: SubmitHandler = async (data) => { - const { title, url, category, description } = data - const imageUrl = `https://via.placeholder.com/300` - const variables = { title, url, category, description, imageUrl } + const { title, url, category, description } = data; + const imageUrl = `https://via.placeholder.com/300`; + const variables = { title, url, category, description, imageUrl }; try { toast.promise(createLink({ variables }), { - loading: 'Creating new link..', - success: 'Link successfully created!🎉', + loading: "Creating new link..", + success: "Link successfully created!🎉", error: `Something went wrong 😥 Please try again - ${error}`, - }) - + }); } catch (error) { - console.error(error) + console.error(error); } - } + }; return (

Create a new link

-
+
- ) -} + ); +}; -export default Admin +export default Admin; export const getServerSideProps: GetServerSideProps = async ({ req, res }) => { const session = await getSession(req, res); @@ -753,17 +800,18 @@ export const getServerSideProps: GetServerSideProps = async ({ req, res }) => { return { redirect: { permanent: false, - destination: '/api/auth/login', + destination: "/api/auth/login", }, props: {}, - } + }; } return { props: {}, }; -} +}; ``` + The `onSubmit` function passes the form values to the `createLink` mutation. A toast will be shown as the mutation is being executed – success, loading, or error. In `getServerSideProps`, if there is no session, you are redirecting the user to the login page. If a user record that matches the email of the logged-in user is found, the `/admin` page is rendered. @@ -797,6 +845,7 @@ const Header = () => { export default Header ``` + You should now be able to create links! 🚀 ### Bonus: protecting pages based on the user role @@ -809,7 +858,7 @@ Firstly, update the `createLink` mutation to check a user's role: // graphql/types/Link.ts builder.mutationField("createLink", (t) => t.prismaField({ - type: 'Link', + type: "Link", args: { title: t.arg.string({ required: true }), description: t.arg.string({ required: true }), @@ -818,20 +867,20 @@ builder.mutationField("createLink", (t) => category: t.arg.string({ required: true }), }, resolve: async (query, _parent, args, ctx) => { - const { title, description, url, imageUrl, category } = args + const { title, description, url, imageUrl, category } = args; if (!(await ctx).user) { - throw new Error("You have to be logged in to perform this action") + throw new Error("You have to be logged in to perform this action"); } const user = await prisma.user.findUnique({ where: { email: (await ctx).user?.email, - } - }) + }, + }); if (!user || user.role !== "ADMIN") { - throw new Error("You don have permission ot perform this action") + throw new Error("You don have permission ot perform this action"); } return prisma.link.create({ @@ -842,12 +891,13 @@ builder.mutationField("createLink", (t) => url, imageUrl, category, - } - }) - } - }) -) + }, + }); + }, + }), +); ``` + Update `admin.tsx` page by adding the role check in your `getServerSideProps` to redirect users that are not admins. Users without the `ADMIN` role will be redirected to the `/404` page. ```tsx @@ -859,7 +909,7 @@ export const getServerSideProps: GetServerSideProps = async ({ req, res }) => { return { redirect: { permanent: false, - destination: '/api/auth/login', + destination: "/api/auth/login", }, props: {}, }; @@ -875,11 +925,11 @@ export const getServerSideProps: GetServerSideProps = async ({ req, res }) => { }, }); - if (!user || user.role !== 'ADMIN') { + if (!user || user.role !== "ADMIN") { return { redirect: { permanent: false, - destination: '/404', + destination: "/404", }, props: {}, }; @@ -890,11 +940,12 @@ export const getServerSideProps: GetServerSideProps = async ({ req, res }) => { }; }; ``` -The default role assigned to a user when signing up is `USER`. So if you try to go to the `/admin` page, it will no longer work. -You can change this by modifying the `role` field of the user in the database. This is very easy to do in Prisma Studio. +The default role assigned to a user when signing up is `USER`. So if you try to go to the `/admin` page, it will no longer work. -First start Prisma Studio by running `npx prisma studio` in the terminal. Then click the **User** model and find the record matching the current user. Now, go ahead and update your user role from `USER` to `ADMIN`. Save your changes by pressing the **Save 1 change** button. +You can change this by modifying the `role` field of the user in the database. This is very easy to do in Prisma Studio. + +First start Prisma Studio by running `npx prisma studio` in the terminal. Then click the **User** model and find the record matching the current user. Now, go ahead and update your user role from `USER` to `ADMIN`. Save your changes by pressing the **Save 1 change** button. ![Prisma Studio – update user role](/fullstack-nextjs-graphql-prisma-3-clxbrcqppv/imgs/prisma-studio-role-update.png) @@ -905,4 +956,3 @@ Navigate to the `/admin` page of your application and voila! You can now create In this part, you learned how to add authentication and authorization to a Next.js app using Auth0 and how you can use Auth0 Actions to add users to your database. Stay tuned for [the next part](/fullstack-nextjs-graphql-prisma-4-1k1kc83x3v) where you'll learn how to add image upload using AWS S3. - diff --git a/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-4-1k1kc83x3v/index.mdx b/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-4-1k1kc83x3v/index.mdx index ccbce97803..2486228919 100644 --- a/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-4-1k1kc83x3v/index.mdx +++ b/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-4-1k1kc83x3v/index.mdx @@ -18,9 +18,10 @@ tags: --- This article is the fourth part of the course where you build a fullstack app with Next.js, GraphQL, TypeScript, - Prisma and PostgreSQL. In this article, you will learn how to add image upload using AWS S3. +Prisma and PostgreSQL. In this article, you will learn how to add image upload using AWS S3. ## Table of Contents + - [Introduction](#introduction) - [Development environment](#development-environment) - [Clone the repository](#clone-the-repository) @@ -28,9 +29,9 @@ This article is the fourth part of the course where you build a fullstack app wi - [Project structure and dependencies](#project-structure-and-dependencies) - [Using AWS S3 to add support for image upload](#using-aws-s3-to-add-support-for-image-upload) - [Create an Identity Access Management user -](#create-an-identity-access-management-user) + ](#create-an-identity-access-management-user) - [Create and configure a new S3 bucket -](#create-and-configure-a-new-s3-bucket) + ](#create-and-configure-a-new-s3-bucket) - [Add image upload functionality to your application](#add-image-upload-functionality-to-your-application) - [Summary and next steps](#summary-and-next-steps) @@ -61,12 +62,14 @@ To get started, navigate into the directory of your choice and run the following ```shell git clone -b part-4 https://github.com/prisma/awesome-links.git ``` + Navigate into the cloned application and install the dependencies: ```shell cd awesome-links npm install ``` + ## Seeding the database After setting up a PostgreSQL database, rename the `env.example` file to `.env` and set the connection string for your database. After that, run the following command to create the tables in your database: @@ -74,11 +77,13 @@ After setting up a PostgreSQL database, rename the `env.example` file to `.env` ```shell npx prisma migrate dev --name init ``` + If `prisma migrate dev` did not trigger the seed step, run the following command to seed the database: ```shell npx prisma db seed ``` + > Refer to [Part 1 – Add Prisma to your Project](/fullstack-nextjs-graphql-prisma-oklidw1rhw#add-prisma-to-your-project) for more details on the format of the connection string. This command will run the `seed.ts` file in the `/prisma` directory. `seed.ts` creates four links and one user in your database using Prisma Client. @@ -132,6 +137,7 @@ awesome-links/ ┣ tailwind.config.js ┗ tsconfig.json ``` + This is a Next.js application that uses the following libraries and tools: - [Prisma](https://www.prisma.io) for database access/CRUD operations @@ -202,6 +208,7 @@ Finally, copy the "Access Key ID" and the "Secret Access Key" and store them in APP_AWS_ACCESS_KEY = '' APP_AWS_SECRET_KEY = '' ``` + ### Create and configure a new S3 bucket The next step is to create an AWS S3 bucket which will store the uploaded objects. You can find the S3 service by looking it up in the search bar or by going to [https://s3.console.aws.amazon.com/](https://s3.console.aws.amazon.com/). @@ -221,6 +228,7 @@ APP_AWS_REGION = '' AWS_S3_BUCKET_NAME = '' # Will be used in an API route. NEXT_PUBLIC_AWS_S3_BUCKET_NAME = '' # Will be used on the client-side ``` + > Note: The bucket name has to be unique and must not contain any spaces or uppercase letters. Go ahead and create the bucket by navigating to the bottom of the page and clicking the **Create bucket** button. You can stick with the defaults settings for now, but you'll update them in the following steps. @@ -236,7 +244,6 @@ Uncheck **Block _all_ public access** and click on **Save changes**. You need to ![AWS S3](/fullstack-nextjs-graphql-prisma-4-1k1kc83x3v/imgs/aws-s3-access-settings.png) - Next, update the resource policy to grant the application access to the Bucket and its contents. In the **Permissions** of your S3 Bucket, navigate to the **Bucket policy** section. Select **Edit** and add the following while changing "name-of-your-bucket" placeholder to the name of your Bucket: ```json @@ -255,6 +262,7 @@ Next, update the resource policy to grant the application access to the Bucket a ] } ``` + ![AWS S3](/fullstack-nextjs-graphql-prisma-4-1k1kc83x3v/imgs/aws-s3-bucket-policy.png) Next, you need to allow your application, which will be on a different domain, to access the stored images. In the **Permissions** tab of your bucket, scroll to the **Cross-origin Resource Sharing (CORS)** section at the bottom and add the following to it: @@ -269,6 +277,7 @@ Next, you need to allow your application, which will be on a different domain, t } ] ``` + ![AWS S3](/fullstack-nextjs-graphql-prisma-4-1k1kc83x3v/imgs/aws-s3-cors.png) > Note: Before deploying your application, ensure you update the "AllowedOrigins" array with the URL pointing to your application. @@ -282,12 +291,13 @@ First, install the `aws-sdk` package by running the following command: ```shell npm install aws-sdk ``` + Next, create a new file called `upload-image.ts` located in the `pages/api/` directory and add the following code to it: ```ts // pages/api/upload-image.ts -import aws from 'aws-sdk' -import type { NextApiRequest, NextApiResponse } from 'next' +import aws from "aws-sdk"; +import type { NextApiRequest, NextApiResponse } from "next"; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { @@ -296,15 +306,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) accessKeyId: process.env.APP_AWS_ACCESS_KEY, secretAccessKey: process.env.APP_AWS_SECRET_KEY, region: process.env.APP_AWS_REGION, - }) + }); // 2. aws.config.update({ accessKeyId: process.env.APP_AWS_ACCESS_KEY, secretAccessKey: process.env.APP_AWS_SECRET_KEY, region: process.env.APP_AWS_REGION, - signatureVersion: 'v4', - }) + signatureVersion: "v4", + }); // 3. const post = await s3.createPresignedPost({ @@ -314,17 +324,18 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) }, Expires: 60, // seconds Conditions: [ - ['content-length-range', 0, 5048576], // up to 1 MB + ["content-length-range", 0, 5048576], // up to 1 MB ], - }) + }); // 4. - return res.status(200).json(post) + return res.status(200).json(post); } catch (error) { - console.log(error) + console.log(error); } } ``` + 1. Creates a new instance of the S3 Bucket 1. Updates the main configuration class with the region, credentials, and additional request options 1. Generates a presigned URL allowing you to write to the S3 Bucket @@ -334,12 +345,12 @@ Finally, update the `pages/admin.tsx` file with the following code: ```tsx // pages/admin.tsx -import React from 'react' -import { type SubmitHandler, useForm } from 'react-hook-form' -import { gql, useMutation } from '@apollo/client' -import toast, { Toaster } from 'react-hot-toast' -import type { GetServerSideProps } from 'next' -import { getSession } from '@auth0/nextjs-auth0' +import React from "react"; +import { type SubmitHandler, useForm } from "react-hook-form"; +import { gql, useMutation } from "@apollo/client"; +import toast, { Toaster } from "react-hot-toast"; +import type { GetServerSideProps } from "next"; +import { getSession } from "@auth0/nextjs-auth0"; type FormValues = { title: string; @@ -347,11 +358,23 @@ type FormValues = { category: string; description: string; image: FileList; -} +}; const CreateLinkMutation = gql` - mutation($title: String!, $url: String!, $imageUrl: String!, $category: String!, $description: String!) { - createLink(title: $title, url: $url, imageUrl: $imageUrl, category: $category, description: $description) { + mutation ( + $title: String! + $url: String! + $imageUrl: String! + $category: String! + $description: String! + ) { + createLink( + title: $title + url: $url + imageUrl: $imageUrl + category: $category + description: $description + ) { title url imageUrl @@ -359,68 +382,71 @@ const CreateLinkMutation = gql` description } } -` +`; const Admin = () => { - const [createLink, { data, loading, error }] = useMutation(CreateLinkMutation) + const [createLink, { data, loading, error }] = useMutation(CreateLinkMutation); const { register, handleSubmit, formState: { errors }, - } = useForm() + } = useForm(); // Upload photo function const uploadPhoto = async (e: React.ChangeEvent) => { - if (!e.target.files || e.target.files.length <= 0) return - const file = e.target.files[0] - const filename = encodeURIComponent(file.name) - const res = await fetch(`/api/upload-image?file=${filename}`) - const data = await res.json() - const formData = new FormData() + if (!e.target.files || e.target.files.length <= 0) return; + const file = e.target.files[0]; + const filename = encodeURIComponent(file.name); + const res = await fetch(`/api/upload-image?file=${filename}`); + const data = await res.json(); + const formData = new FormData(); Object.entries({ ...data.fields, file }).forEach(([key, value]) => { // @ts-ignore - formData.append(key, value) - }) + formData.append(key, value); + }); toast.promise( fetch(data.url, { - method: 'POST', + method: "POST", body: formData, }), { - loading: 'Uploading...', - success: 'Image successfully uploaded!🎉', + loading: "Uploading...", + success: "Image successfully uploaded!🎉", error: `Upload failed 😥 Please try again ${error}`, }, - ) - } + ); + }; const onSubmit: SubmitHandler = async (data) => { - const { title, url, category, description, image } = data - const imageUrl = `https://${process.env.NEXT_PUBLIC_AWS_S3_BUCKET_NAME}.s3.amazonaws.com/${image[0]?.name}` - const variables = { title, url, category, description, imageUrl } + const { title, url, category, description, image } = data; + const imageUrl = `https://${process.env.NEXT_PUBLIC_AWS_S3_BUCKET_NAME}.s3.amazonaws.com/${image[0]?.name}`; + const variables = { title, url, category, description, imageUrl }; try { toast.promise(createLink({ variables }), { - loading: 'Creating new link..', - success: 'Link successfully created!🎉', + loading: "Creating new link..", + success: "Link successfully created!🎉", error: `Something went wrong 😥 Please try again - ${error}`, - }) + }); } catch (error) { - console.error(error) + console.error(error); } - } + }; return (

Create a new link

-
+
- ) -} + ); +}; -export default Admin +export default Admin; // getServerSideProps code remains unchanged ``` + The form includes a new input field to handle file upload. The input field accepts images of either `.png` or `.jpeg` formats. Whenever an image is uploaded, the `uploadPhoto` function sends a request to the `/api/upload-image` API endpoint. A toast will be shown as the request is being resolved by the API – success, loading, or error states. When the form is submitted, the URL of the image is included as a variable in the `createLink` mutation. A toast will appear as the mutation is being executed. @@ -504,4 +531,3 @@ When the form is submitted, the URL of the image is included as a variable in th ## Summary and next steps You learned how to add support for image upload using AWS S3. In the [next part](/fullstack-nextjs-graphql-prisma-5-m2fna60h7c), you will deploy your app to Vercel and learn how you can use the Prisma Data Proxy to manage your database connection pool to ensure your application doesn't run out of connections. - diff --git a/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-5-m2fna60h7c/index.mdx b/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-5-m2fna60h7c/index.mdx index 6c5984fb26..ec90067d43 100644 --- a/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-5-m2fna60h7c/index.mdx +++ b/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-5-m2fna60h7c/index.mdx @@ -96,11 +96,13 @@ Before you deploy the application, you will make a few changes. Before you deploy your application, you will need to make a few updates to your application to make it work with the Prisma Data Proxy. First, update your `.env` file by renaming the existing `DATABASE_URL` to `MIGRATE_DATABASE_URL`. Create a `DATABASE_URL` variable and set the Prisma Data Proxy URL from the previous step here: + ``` # .env MIGRATE_DATABASE_URL="postgres://" DATABASE_URL="prisma://" ``` + The `MIGRATE_DATABASE_URL` will be used for making database schema changes to your database. ### Create new scripts in `package.json` @@ -113,6 +115,7 @@ Next, update your `package.json` file by adding a `vercel-build` script: "vercel-build": "npx prisma generate --data-proxy && next build", }, ``` + The `vercel-build` script will generate Prisma Client that uses the Prisma Data Proxy and build the application. ## Deploy the app to Vercel @@ -133,10 +136,10 @@ Refer to the `.env.example` file in the repository for the environment variables Once you've added the environment variables, click **Deploy**. - ![Configuring environment variables](/fullstack-nextjs-graphql-prisma-5-m2fna60h7c/imgs/vercel-environment-variables.png) Once your application is successfully deployed, copy its URL and: + - Update the **Allowed Callback URLs** and **Allowed Logout URLs** on the Auth0 Dashboard with the URL of your application - Update your Auth0 Action with the URL of the deployed application - Update the **AllowedOrigins** Cross-origin Resource Sharing (CORS) policy on S3 with the URL to your deployed application @@ -150,6 +153,7 @@ If everything works correctly, you will be able to view your deployed applicatio This article concludes the series. You learned how to build a full-stack app using modern tools that offer great developer experience and leveraged different services to get your application production-ready. You: + - Explored database modeling using Prisma - Built a GraphQL API using GraphQL Yoga and Pothos - Added authentication using Auth0 @@ -160,4 +164,3 @@ You: You can find the complete source code for the app on [GitHub](https://github.com/prisma/awesome-links). Feel free to raise issues or contribute to the repository if you find any bugs or want to make improvements. Feel free to reach out on [Twitter](https://twitter.com/thisismahmoud_) if you have any questions. - diff --git a/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-oklidw1rhw/index.mdx b/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-oklidw1rhw/index.mdx index 63eedb1fc1..07ec4b4958 100644 --- a/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-oklidw1rhw/index.mdx +++ b/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-oklidw1rhw/index.mdx @@ -112,6 +112,7 @@ To get started, navigate into the directory of your choice and run the following ```shell git clone -b part-1 https://github.com/m-abdelwahab/awesome-links.git ``` + You can now navigate into the cloned directory, install the dependencies and start the development server: ```shell @@ -119,6 +120,7 @@ cd awesome-links npm install npm run dev ``` + Here's what the starter project looks like: ![Current state of the application](/fullstack-nextjs-graphql-prisma-oklidw1rhw/imgs/awesome-links-starter-project.png) @@ -150,6 +152,7 @@ awesome-links/ ┣ tailwind.config.js ┗ tsconfig.json ``` + This starter project is a Next.js app with TypeScript and TailwindCSS installed. @@ -163,17 +166,18 @@ The `_app.tsx` file is used to override the default `App` behavior. This file al ```tsx // pages/_app.tsx -import '../styles/tailwind.css' // import Tailwind globally -import Layout from '../components/Layout' // header layout persists between page changes +import "../styles/tailwind.css"; // import Tailwind globally +import Layout from "../components/Layout"; // header layout persists between page changes function MyApp({ Component, pageProps }) { return ( - ) + ); } -export default MyApp +export default MyApp; ``` + The data we see when navigating to `http://localhost:3000` is hardcoded in the [`/data/links.ts`](https://github.com/m-abdelwahab/awesome-links/blob/part-1/data/links.ts) file. In the upcoming parts, the data will be fetched dynamically from the database using a GraphQL API. ## Creating the data model for the app @@ -196,21 +200,25 @@ To get started, first install Prisma's CLI as a development dependency: ```shell npm install --save-dev prisma ``` + You can now use the Prisma CLI to create a basic Prisma setup by running: ```shell npx prisma init ``` + A new `/prisma` directory is created and inside it you will find a `schema.prisma` file. This is your main Prisma configuration file which will contain your database schema. A `.env` ([dotenv](https://github.com/motdotla/dotenv)) file is also added to the root of the project. This is where you define environment variables such as the database connection URL or access tokens. -Open the `.env` file and replace the dummy connection URL with the connection URL of your PostgreSQL database. +Open the `.env` file and replace the dummy connection URL with the connection URL of your PostgreSQL database. + ``` // .env # Example: postgresql://giwuzwpdnrgtzv:d003c6a604bb400ea955c3abd8c16cc98f2d909283c322ebd8e9164b33ccdb75@ec2-54-170-123-247.eu-west-1.compute.amazonaws.com:5432/d6ajekcigbuca9 DATABASE_URL="" ``` + The database URL you just added has the following structure: ![Database URL breakdown](/fullstack-nextjs-graphql-prisma-oklidw1rhw/imgs/database-url-breakdown.png) @@ -239,6 +247,7 @@ generator client { provider = "prisma-client-js" } ``` + > **Note**: This file uses PSL (Prisma Schema Language). To get the best possible development experience, make sure you install our [VSCode extension](https://marketplace.visualstudio.com/items?itemName=Prisma.prisma), which adds syntax highlighting, formatting, auto-completion, jump-to-definition, and linting for `.prisma` files. In the `datasource` field, we specified that we're using PostgreSQL and that we're loading the database URL from the `.env` file. @@ -268,6 +277,7 @@ enum Role { ADMIN } ``` + > **Note**: models are typically spelled in [PascalCase](https://wiki.c2.com/?pascalcase) and should use the singular form. (for example, `User` instead of `user`, `users` or `Users`) Here we defined a `User` model with several fields. Each field has a name followed by a type and [optional field attributes](https://www.prisma.io/docs/orm/prisma-schema/data-model/models#defining-fields). @@ -292,6 +302,7 @@ model Link { category String } ``` + ```prisma @@ -299,30 +310,31 @@ model Link { // code above unchanged model User { - id Int @id @default(autoincrement()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - email String? @unique - image String? - role Role @default(USER) +id Int @id @default(autoincrement()) +createdAt DateTime @default(now()) +updatedAt DateTime @updatedAt +email String? @unique +image String? +role Role @default(USER) } enum Role { - USER - ADMIN +USER +ADMIN } model Link { - id Int @id @default(autoincrement()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - title String - description String - url String - imageUrl String - category String +id Int @id @default(autoincrement()) +createdAt DateTime @default(now()) +updatedAt DateTime @updatedAt +title String +description String +url String +imageUrl String +category String } -``` + +```` @@ -362,13 +374,14 @@ model Link { category String + users User[] } -``` +```` + This is an [_implicit_ many-to-many](https://www.prisma.io/docs/orm/prisma-schema/data-model/relations#implicit-many-to-many-relations) relation, where we have a relation table in the underlying database. This [relation table](https://www.prisma.io/docs/orm/prisma-schema/data-model/relations#relation-tables) is managed by Prisma. Here's what the final schema looks like: ```prisma -copy +copy // prisma/schema.prisma datasource db { @@ -407,6 +420,7 @@ model Link { users User[] } ``` + ## Migrating and pushing changes to the database To create these tables in the database, you will use the `prisma migrate dev` command: @@ -414,7 +428,9 @@ To create these tables in the database, you will use the `prisma migrate dev` co ```shell npx prisma migrate dev --name init ``` + The command does the following things: + - Generate a new SQL migration called `init` - Apply the migration to the database - Install Prisma Client if it's not yet installed @@ -425,6 +441,7 @@ If Prisma Client is not automatically installed, you can install it with the fol ```shell npm install @prisma/client ``` + Inside the `prisma` directory, you will notice a new folder called `migrations`. It should also contain another folder that ends with `init` and contains a file called `migration.sql`. The `migration.sql` file contains the generated SQL. @@ -435,51 +452,54 @@ CREATE TYPE "Role" AS ENUM ('USER', 'ADMIN'); -- CreateTable CREATE TABLE "User" ( - "id" SERIAL NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - "email" TEXT, - "image" TEXT, - "role" "Role" NOT NULL DEFAULT 'USER', +"id" SERIAL NOT NULL, +"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, +"updatedAt" TIMESTAMP(3) NOT NULL, +"email" TEXT, +"image" TEXT, +"role" "Role" NOT NULL DEFAULT 'USER', CONSTRAINT "User_pkey" PRIMARY KEY ("id") + ); -- CreateTable CREATE TABLE "Link" ( - "id" SERIAL NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - "title" TEXT NOT NULL, - "description" TEXT NOT NULL, - "url" TEXT NOT NULL, - "imageUrl" TEXT NOT NULL, - "category" TEXT NOT NULL, +"id" SERIAL NOT NULL, +"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, +"updatedAt" TIMESTAMP(3) NOT NULL, +"title" TEXT NOT NULL, +"description" TEXT NOT NULL, +"url" TEXT NOT NULL, +"imageUrl" TEXT NOT NULL, +"category" TEXT NOT NULL, CONSTRAINT "Link_pkey" PRIMARY KEY ("id") + ); -- CreateTable -CREATE TABLE "_LinkToUser" ( - "A" INTEGER NOT NULL, - "B" INTEGER NOT NULL +CREATE TABLE "\_LinkToUser" ( +"A" INTEGER NOT NULL, +"B" INTEGER NOT NULL ); -- CreateIndex CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); -- CreateIndex -CREATE UNIQUE INDEX "_LinkToUser_AB_unique" ON "_LinkToUser"("A", "B"); +CREATE UNIQUE INDEX "\_LinkToUser_AB_unique" ON "\_LinkToUser"("A", "B"); -- CreateIndex -CREATE INDEX "_LinkToUser_B_index" ON "_LinkToUser"("B"); +CREATE INDEX "\_LinkToUser_B_index" ON "\_LinkToUser"("B"); -- AddForeignKey -ALTER TABLE "_LinkToUser" ADD CONSTRAINT "_LinkToUser_A_fkey" FOREIGN KEY ("A") REFERENCES "Link"("id") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "\_LinkToUser" ADD CONSTRAINT "\_LinkToUser_A_fkey" FOREIGN KEY ("A") REFERENCES "Link"("id") ON DELETE CASCADE ON UPDATE CASCADE; -- AddForeignKey -ALTER TABLE "_LinkToUser" ADD CONSTRAINT "_LinkToUser_B_fkey" FOREIGN KEY ("B") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; -``` +ALTER TABLE "\_LinkToUser" ADD CONSTRAINT "\_LinkToUser_B_fkey" FOREIGN KEY ("B") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +```` @@ -517,7 +537,8 @@ main() .finally(async () => { await prisma.$disconnect() }) -``` +```` + We are first creating a user using the [`create()`](https://www.prisma.io/docs/orm/reference/prisma-client-reference#create) function, which creates a new database record. Next, we are using the [`createMany()`](https://www.prisma.io/docs/orm/reference/prisma-client-reference#createmany) function to create multiple records. We are passing the hard-coded data we have as a parameter. @@ -527,6 +548,7 @@ By default, Next.js [forces the use of `ESNext` modules](https://github.com/verc ```shell npm install --save-dev ts-node ``` + Then in the `tsconfig.json` file, specify that `ts-node` will use `CommonJS` modules: ```json @@ -549,9 +571,10 @@ diff + } } ``` + Update your `package.json` file by adding a `prisma` key with a `seed` property defining the script for seeding the database: -```json +````json diff { // ... "devDependencies": { @@ -575,7 +598,8 @@ You can now seed your database by running the following command: ```shell npx prisma db seed -``` +```` + If everything worked correctly you should see the following output: ```shell @@ -585,6 +609,7 @@ Running seed: ts-node --compiler-options '{"module":"CommonJS"}' "prisma/seed.ts 🌱 Your database has been seeded. ``` + ## Use Prisma Studio to explore your database @@ -596,6 +621,7 @@ To start Prisma Studio, run the following command ```shell npx prisma studio ``` + If you've done all the steps correctly you should you have the `Link` and `User` models inside your database. Inside the `Link` model you'll find 4 records and for the `User` model you'll find 1 record. ![Prisma Studio](/fullstack-nextjs-graphql-prisma-oklidw1rhw/imgs/awesome-links-prisma-studio.png) @@ -610,4 +636,3 @@ In [the next part](/fullstack-nextjs-graphql-prisma-2-fwpc6ds155) of the course, - Building a GraphQL API for our app using GraphQL Yoga and Pothos - Consuming the API on the client using Apollo Client. - GraphQL pagination so that we don't load all links at once and have better performance. - diff --git a/apps/blog/content/blog/fullstack-remix-prisma-mongodb-1-7d0bftxbmb6r/index.mdx b/apps/blog/content/blog/fullstack-remix-prisma-mongodb-1-7d0bftxbmb6r/index.mdx index cee459f00b..103871aabf 100644 --- a/apps/blog/content/blog/fullstack-remix-prisma-mongodb-1-7d0bftxbmb6r/index.mdx +++ b/apps/blog/content/blog/fullstack-remix-prisma-mongodb-1-7d0bftxbmb6r/index.mdx @@ -109,6 +109,7 @@ To start off a Remix project, run the following command in a location where you ```shell npx create-remix@latest kudos ``` + This will scaffold a starter project for you and ask you a couple of questions. Choose the following options to let Remix know you want a blank project using TypeScript and you intend to deploy it to Vercel. - What type of app do you want to create? **Just the basics** @@ -121,6 +122,7 @@ This will scaffold a starter project for you and ask you a couple of questions. Once the project is set up, go ahead and pop it open by either opening the project in your code editor or by running the command `code .` within that folder in your terminal if you are using [VSCode's CLI](https://code.visualstudio.com/docs/editor/command-line). You will see the generated boilerplate project with a file structure that looks like this: + ``` │── app │ ├── entry.client.tsx @@ -141,6 +143,7 @@ You will see the generated boilerplate project with a file structure that looks ├── README.md └── vercel.json ``` + For the majority of this series, you will be working within the `app` directory, which will hold all of the custom code for this application. Any file within `./app/routes` will be turned into a route. For example, assuming your application is running on `localhost:3000`, the `./app/routes/index.tsx` file will result in a generated route at `localhost:3000/`. If you were to create another file at `app/routes/home.tsx`, Remix would generate a `localhost:3000/home` route in your site. @@ -166,6 +169,7 @@ To start things off, there are a few dependencies you will need in order to use ```shell npm install -D tailwindcss postcss autoprefixer concurrently ``` + This will install the following development dependencies: - [`tailwindcss`](https://tailwindcss.com/): The command-line interface _(CLI)_ that allows you to initialize a Tailwind configuration. @@ -178,6 +182,7 @@ Once those are installed, you can initialize Tailwind in the project: ```shell npx tailwindcss init -p ``` + This will generate two files: - `tailwind.config.js`: This is where you can tweak and extend TailwindCSS. See all of the options [here](https://tailwindcss.com/docs/configuration). @@ -186,18 +191,17 @@ This will generate two files: When a build is run, Tailwind will scan through the codebase to determine which of its utility classes it needs to bundle into its generated output. You will need to let Tailwind know which files it should look at to determine this. In `tailwind.config.js`, add the following glob pattern to the `content` key: ```js -diff +diff; // tailwind.config.js module.exports = { - content: [ -+ "./app/**/*.{js,ts,jsx,tsx}", - ], + content: [+"./app/**/*.{js,ts,jsx,tsx}"], theme: { extend: {}, }, plugins: [], -} +}; ``` + This will tell Tailwind that any file inside of the `app` folder with the provided extensions should be scanned through for keywords and class names that Tailwind will pick up on to generate its output file. Next, in `package.json` update your `scripts` section to include a build process for Tailwind when the application is built and when the development server runs. Add the following scripts: @@ -213,6 +217,7 @@ Next, in `package.json` update your `scripts` section to include a build process } } ``` + You may notice a few of the scripts are pointing to a file at `./styles/app.css` that does not exist yet. This will be Tailwind's source file when it is built and where you will import the various [functions and directives](https://tailwindcss.com/docs/functions-and-directives) Tailwind will use. Go ahead and create that source file at `./styles/app.css` and add each of Tailwind's [layers](https://tailwindcss.com/docs/adding-custom-styles#using-css-and-layer) using the [`@tailwind`](https://tailwindcss.com/docs/functions-and-directives#tailwind) directive: @@ -223,6 +228,7 @@ Go ahead and create that source file at `./styles/app.css` and add each of Tailw @tailwind components; @tailwind utilities; ``` + Now when the application is run or built, your `scripts` will also kick off the process to run Tailwind's scanning and building process. The result of this will be outputted into `app/styles/app.css`. That file is what you will import into your Remix application to allow you to use Tailwind in your code! @@ -235,17 +241,18 @@ In `app/root.tsx`, import the generated stylesheet and export a [`links`](https: import type { MetaFunction, LinksFunction } from "@remix-run/node"; // 2 -import styles from './styles/app.css'; +import styles from "./styles/app.css"; // ... // 3 export const links: LinksFunction = () => { - return [{ rel: 'stylesheet', href: styles }] -} + return [{ rel: "stylesheet", href: styles }]; +}; // ... ``` + The code above will: 1. Import the type for Remix's `links` function. @@ -263,9 +270,10 @@ export default function Index() {

TailwindCSS Is Working!

- ) + ); } ``` + You should see a screen that looks something like this: ![](/fullstack-remix-prisma-mongodb-1-7d0bftxbmb6r/imgs/tailwind-css-checkpoint.png) @@ -288,13 +296,14 @@ Head over to the Atlas home page linked above. If you don't already have an acco If you will be using an existing account, head to the dashboard. From there you will see a dropdown in the top left corner of the screen. If you pop that open you will see the option New Project. - ![](/fullstack-remix-prisma-mongodb-1-7d0bftxbmb6r/imgs/new-project.png) +![](/fullstack-remix-prisma-mongodb-1-7d0bftxbmb6r/imgs/new-project.png) + +Once you click on that, hit the **Build a Database** button. - Once you click on that, hit the **Build a Database** button. +![](/fullstack-remix-prisma-mongodb-1-7d0bftxbmb6r/imgs/build-a-database.png) - ![](/fullstack-remix-prisma-mongodb-1-7d0bftxbmb6r/imgs/build-a-database.png) +From there you should be able to follow along with the rest of the steps below. - From there you should be able to follow along with the rest of the steps below. @@ -316,7 +325,7 @@ Then, in the **Where would you like to connect from?** section, hit **Add My Cur ![](/fullstack-remix-prisma-mongodb-1-7d0bftxbmb6r/imgs/mongodb-ip-setup.png) -With those steps completed, your database should finish its provisioning process within a few minutes *(at most)* and be ready for you to play with! +With those steps completed, your database should finish its provisioning process within a few minutes _(at most)_ and be ready for you to play with! ## Set up Prisma @@ -329,11 +338,13 @@ The first thing you will want to do is install the [Prisma CLI](https://www.pris ```shell npm i -D prisma ``` + To initialize Prisma within the project, simply run: ```shell npx prisma init --datasource-provider mongodb ``` + This will create a few different files in your project. You will see a `prisma` folder with a `schema.prisma` file inside of it. This is where you will define your schema and model out your data. It will also generate a `.env` file automatically if one did not previously exist with a sample environment variable that will hold your database's connection string. @@ -354,6 +365,7 @@ datasource db { url = env("DATABASE_URL") } ``` + > **Note**: This file is written in PSL (Prisma Schema Language), which allows you to map out your schema. For more information on Prisma schemas and PSL, check out the [Prisma docs](https://www.prisma.io/docs/orm/prisma-schema). In the `url` of the [`datasource`](https://www.prisma.io/docs/orm/prisma-schema/overview/data-sources) block, you can see it references the `DATABASE_URL` environment variable from the `.env` file using the `env()` function PSL provides. Prisma uses [dotenv](https://www.npmjs.com/package/dotenv) under the hood to expose those variables to Prisma. @@ -379,11 +391,13 @@ In your `.env` file, replace the default connection string with your MongoDB con ```shell mongodb+srv://USERNAME:PASSWORD@HOST:PORT/DATABASE ``` + After pasting in your connection string and modifying it to match the above format, you should be left with a string that looks like this: ```shell mongodb+srv://sadams:@cluster0.vv1we.mongodb.net/kudos?retryWrites=true&w=majority ``` + > **Note**: Notice the `kudos` database name. You can put any name you want for your `DATABASE` here. MongoDB will automatically create the new database if it does not already exist. > > For more details on connecting to your MongoDB database, check out the [docs](https://www.prisma.io/docs/getting-started/setup-prisma/add-to-existing-project/mongodb/connect-your-database-typescript-mongodb). @@ -402,6 +416,7 @@ model User { } ``` + > **Note**: MongoDB is a schemaless database built for flexible data so it may seem counterintuitive to define a "schema" for the data you are storing in it. As schemaless databases grow and evolve, however, the problem occurs where it becomes difficult to keep track of what data lives where while accounting for legacy data shapes. Because of this, defining a schema may save some headaches in the long run. Every Prisma model needs to have a unique `id` field. @@ -412,6 +427,7 @@ model User { id String @id @default(auto()) @map("_id") @db.ObjectId } ``` + The code above will create an `id` field and let Prisma know this is a unique identifier with the [`@id`](https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#id) attribute. Because MongoDB automatically creates an `_id` field for every collection, you will let Prisma know using the [`@map`](https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#map) attribute that while you are calling this field `id` in the schema, it should map to the `_id` field in the database. The code will also define the data type for your `id` field and set a default value of `auto()`, which will allow you to make use of MongoDB's automatically generated unique IDs. @@ -430,6 +446,7 @@ model User { password String } ``` + As you can see above, you will be adding two `DateTime` type fields that will keep track of when a user gets created and when it is updated. The [`@updatedAt`](https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#updatedat) attribute will automatically update that field with a current timestamp any time that user is updated. It will also add an `email` field of type `String` that must be unique, indicated by the [`@unique`](https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#unique) attribute. This means no other user can have the same email. @@ -445,13 +462,16 @@ After making changes to our schema you can run the command: ```shell npx prisma db push ``` + This will push your schema changes to MongoDB, creating any new collections or indexes you have defined. For example, when you push your schema as it is now, you should see the following in the output: + ``` Applying the following changes: [+] Collection `User` [+] Unique index `User_email_key` on ({"email":1}) ``` + Because MongoDB is _schemaless_, there is no real concept of _migrations_. A schemaless database's data can fluidly change and evolve as the application's scope grows and changes. This command simply creates the defined collections and indexes. ![](/fullstack-remix-prisma-mongodb-1-7d0bftxbmb6r/imgs/user-collection.png) @@ -466,4 +486,3 @@ In the next article you will learn about: - Storing and modifying user data with Prisma and MongoDB - Building a Login form - Building a Signup form - diff --git a/apps/blog/content/blog/fullstack-remix-prisma-mongodb-2-ZTmOy58p4re8/index.mdx b/apps/blog/content/blog/fullstack-remix-prisma-mongodb-2-ZTmOy58p4re8/index.mdx index dfa1d1ad6b..bb97ecf5b6 100644 --- a/apps/blog/content/blog/fullstack-remix-prisma-mongodb-2-ZTmOy58p4re8/index.mdx +++ b/apps/blog/content/blog/fullstack-remix-prisma-mongodb-2-ZTmOy58p4re8/index.mdx @@ -79,9 +79,10 @@ export default function Login() {

Login Route

- ) + ); } ``` + The default export of a route file is the component Remix renders into the browser. Start the development server using `npm run dev` and navigate to [`http://localhost:3000/login`](http://localhost:3000/login), and you should see the route rendered. @@ -98,18 +99,18 @@ First, create a component you will wrap your routes in to provide some shared fo ### Composition - **Composition** is a pattern where you provide a component a set of child elements via its `props`. The `children` prop represents the elements defined between the opening and closing tag of the parent component. For example, consider this usage of a component named `Parent`: +**Composition** is a pattern where you provide a component a set of child elements via its `props`. The `children` prop represents the elements defined between the opening and closing tag of the parent component. For example, consider this usage of a component named `Parent`: + +```tsx +

The child

+
+``` - ```tsx -

The child

-
- ``` +In this case, the `

` tag is a child of the `Parent` component and will be rendered into the `Parent` component wherever you decide to render the `children` prop value. - In this case, the `

` tag is a child of the `Parent` component and will be rendered into the `Parent` component wherever you decide to render the `children` prop value. - To see this in action, create a new folder inside the `app` folder named `components`. Inside of that folder create a new file named `layout.tsx`. In that file, export the following [function component](https://reactjs.org/docs/components-and-props.html): @@ -117,50 +118,50 @@ In that file, export the following [function component](https://reactjs.org/docs ```tsx // app/components/layout.tsx export function Layout({ children }: { children: React.ReactNode }) { - return

{children}
+ return
{children}
; } ``` + This component uses Tailwind classes to specify you want anything wrapped in the component to take up the full width and height of the screen, use the mono font, and show a moderately dark blue as the background. Notice the `children` prop is rendered inside the `
`. To see how this will get rendered when put to use, check out the snippets below: - - ```tsx

Child Element

``` + ```tsx

Child Element

``` - ## Create the sign in form Now you can import that component into the `app/routes/login.tsx` file and wrap your `

` tag inside of the new `Layout` component instead of the `
` where it currently lives: ```tsx // app/routes/login.tsx -import { Layout } from '~/components/layout' +import { Layout } from "~/components/layout"; export default function Login() { return (

Login Route

- ) + ); } ``` + ### Build the form Next add a sign in form that takes in `email` and `password` inputs and displays a submit button. Add a nice welcome message at the top to greet users when they enter your site and center the entire form on the screen using [Tailwind's flex classes](https://tailwindcss.com/docs/flex). ```tsx // app/routes/login.tsx -import { Layout } from '~/components/layout' +import { Layout } from "~/components/layout"; export default function Login() { return ( @@ -178,7 +179,12 @@ export default function Login() { - +
- ) + ); } ``` + ![](/fullstack-remix-prisma-mongodb-2-ZTmOy58p4re8/imgs/login-form.png) At this point, you don't need to worry about where the `
`'s action is pointing, just that it has a `method` value of `"post"`. Later on you will check out some cool Remix magic that sets up the action for us! @@ -206,14 +213,20 @@ Create a new file in `app/components` named `form-field.tsx` where you will buil ```tsx // app/components/form-field.tsx interface FormFieldProps { - htmlFor: string - label: string - type?: string - value: any - onChange?: (...args: any) => any + htmlFor: string; + label: string; + type?: string; + value: any; + onChange?: (...args: any) => any; } -export function FormField({ htmlFor, label, type = 'text', value, onChange = () => {} }: FormFieldProps) { +export function FormField({ + htmlFor, + label, + type = "text", + value, + onChange = () => {}, +}: FormFieldProps) { return ( <>
- ) + ); } ``` + Two changes were made here: 1. You added two new keys to the `formData` state. @@ -446,6 +468,7 @@ Before moving on, however, you will need a new dependency in your project. Run t ```shell npm i bcryptjs && npm i -D @types/bcryptjs ``` + This installs the [`bcryptjs`](https://www.npmjs.com/package/bcryptjs) library and its type definitions. You will use this later on to hash and compare passwords. Authentication will be session-based, following the same patterns used in the [authentication](https://remix.run/docs/en/v1/tutorials/jokes#authentication) for Remix's [Jokes App](https://remix.run/docs/en/v1/tutorials/jokes) tutorial. @@ -479,24 +502,27 @@ Export an async function from `app/utils/auth.server.ts` named `register`: // app/utils/auth.server.ts export async function register() {} ``` + Create and export a `type` defining the fields the register form will provide in another new file within `app/utils` named `types.server.ts`. ```tsx // app/utils/types.server.ts export type RegisterForm = { - email: string - password: string - firstName: string - lastName: string -} + email: string; + password: string; + firstName: string; + lastName: string; +}; ``` + Import that `type` into `app/utils/auth.server.ts` and use it in the `register` function to describe a `user` parameter, which will contain the sign up form's data: ```tsx // app/utils/auth.server.ts -import type { RegisterForm } from './types.server' +import type { RegisterForm } from "./types.server"; export async function register(user: RegisterForm) {} ``` + When this `register` function is called and supplied a `user`, the first thing you will need to check is whether or not a user already exists with the email provided. > **Note**: Remember, the `email` field is defined as unique in your schema. @@ -509,26 +535,27 @@ Create a new file in the `app/utils` folder named `prisma.server.ts` where you w ```tsx // app/utils/prisma.server.ts -import { PrismaClient } from '@prisma/client' +import { PrismaClient } from "@prisma/client"; -let prisma: PrismaClient +let prisma: PrismaClient; declare global { - var __db: PrismaClient | undefined + var __db: PrismaClient | undefined; } -if (process.env.NODE_ENV === 'production') { - prisma = new PrismaClient() - prisma.$connect() +if (process.env.NODE_ENV === "production") { + prisma = new PrismaClient(); + prisma.$connect(); } else { if (!global.__db) { - global.__db = new PrismaClient() - global.__db.$connect() + global.__db = new PrismaClient(); + global.__db.$connect(); } - prisma = global.__db + prisma = global.__db; } -export { prisma } +export { prisma }; ``` + > **Note**: There are precautions put into place above that prevent live-reloads from saturating your database with connections while developing. You now have a way to access your database. In `app/utils/auth.server.ts`, import the instantiated `PrismaClient` and add the following to the `register` function: @@ -536,17 +563,18 @@ You now have a way to access your database. In `app/utils/auth.server.ts`, impor ```tsx // app/utils/auth.server.ts -import type { RegisterForm } from './types.server' -import { prisma } from './prisma.server' -import { json } from '@remix-run/node' +import type { RegisterForm } from "./types.server"; +import { prisma } from "./prisma.server"; +import { json } from "@remix-run/node"; export async function register(user: RegisterForm) { - const exists = await prisma.user.count({ where: { email: user.email } }) + const exists = await prisma.user.count({ where: { email: user.email } }); if (exists) { - return json({ error: `User already exists with that email` }, { status: 400 }) + return json({ error: `User already exists with that email` }, { status: 400 }); } } ``` + The register funciton will now query for any user in your database with the email provided. The `count` function was used here becuase it returns a numeric value. If there are no records matching the query, it will return `0` which evaluates to `false`. Otherwise, a value greater than `0` will be returned which evaluates to `true`. @@ -571,6 +599,7 @@ type Profile { lastName String } ``` + The `type` keyword is used to define a composite type – allowing you to define a document inside the document. The benefit of using a composite type over a JSON type is that you get type-safety when querying documents. This is _super_ helpful as it gives you the capability of explicitly defining the shape of data that would otherwise have been fluid and capable of containing anything due to MongoDB's flexible nature. @@ -593,11 +622,13 @@ model User { // ... ``` + Awesome, your `User` model will now contain a `profile` embedded document. Re-generate Prisma Client to account for these new changes: ```shell npx prisma generate ``` + > **Note**: You do not need to run `prisma db push` because you have not added any new collections or indexes. ### Add a user service @@ -606,12 +637,12 @@ Create another file in `app/utils` named `user.server.ts` where any user-specifi ```tsx // app/utils/user.server.ts -import bcrypt from 'bcryptjs' -import type { RegisterForm } from './types.server' -import { prisma } from './prisma.server' +import bcrypt from "bcryptjs"; +import type { RegisterForm } from "./types.server"; +import { prisma } from "./prisma.server"; export const createUser = async (user: RegisterForm) => { - const passwordHash = await bcrypt.hash(user.password, 10) + const passwordHash = await bcrypt.hash(user.password, 10); const newUser = await prisma.user.create({ data: { email: user.email, @@ -621,10 +652,11 @@ export const createUser = async (user: RegisterForm) => { lastName: user.lastName, }, }, - }) - return { id: newUser.id, email: user.email } -} + }); + return { id: newUser.id, email: user.email }; +}; ``` + This `createUser` function does a couple of things: 1. It hashes the password provided in the registration form because you should not store it as plain-text. @@ -665,6 +697,7 @@ export async function register(user: RegisterForm) { + } } ``` + Now, when a user registers, if another user does not already exist with the provided email a new one will be created. If something goes wrong during the creation of the user, an error will be returned to the client along with the values that were passed in for the `email` and `password`. ## Build the login function @@ -677,18 +710,19 @@ The `login` function will take in an `email` and a `password`, so to start this // ... export type LoginForm = { - email: string - password: string -} + email: string; + password: string; +}; ``` + Then create the `login` function by adding the following to `app/utils/auth.server.ts`: ```ts // app/utils/auth.server.ts // 1 -import { RegisterForm, LoginForm } from './types.server' -import bcrypt from 'bcryptjs' +import { RegisterForm, LoginForm } from "./types.server"; +import bcrypt from "bcryptjs"; //... @@ -696,16 +730,17 @@ export async function login({ email, password }: LoginForm) { // 2 const user = await prisma.user.findUnique({ where: { email }, - }) + }); // 3 if (!user || !(await bcrypt.compare(password, user.password))) - return json({ error: `Incorrect login` }, { status: 400 }) + return json({ error: `Incorrect login` }, { status: 400 }); // 4 - return { id: user.id, email } + return { id: user.id, email }; } ``` + The code above ... 1. ... imports the new `type` and the `bcryptjs` library. @@ -725,29 +760,30 @@ Import that function into `app/utils/auth.server.ts` and add a new cookie sessio // app/utils/auth.server.ts // Added the createCookieSessionStorage function 👇 -import { json, createCookieSessionStorage } from '@remix-run/node' +import { json, createCookieSessionStorage } from "@remix-run/node"; // ... -const sessionSecret = process.env.SESSION_SECRET +const sessionSecret = process.env.SESSION_SECRET; if (!sessionSecret) { - throw new Error('SESSION_SECRET must be set') + throw new Error("SESSION_SECRET must be set"); } const storage = createCookieSessionStorage({ cookie: { - name: 'kudos-session', - secure: process.env.NODE_ENV === 'production', + name: "kudos-session", + secure: process.env.NODE_ENV === "production", secrets: [sessionSecret], - sameSite: 'lax', - path: '/', + sameSite: "lax", + path: "/", maxAge: 60 * 60 * 24 * 30, httpOnly: true, }, -}) +}); // login & register functions... ``` + The code above creates a session storage with a couple of settings: - `name`: The name of the cookie. @@ -766,26 +802,28 @@ You will also need to set up a session secret in the `.env` file. Add a variable // .env SESSION_SECRET="supersecretvalue" ``` + The session storage is now set up. Create one more function in `app/utils/auth.server.ts` that will actually create the cookie session: ```ts // app/utils/auth.server.ts // 👇 Added the redirect function -import { redirect, json, createCookieSessionStorage } from '@remix-run/node' +import { redirect, json, createCookieSessionStorage } from "@remix-run/node"; // ... export async function createUserSession(userId: string, redirectTo: string) { - const session = await storage.getSession() - session.set('userId', userId) + const session = await storage.getSession(); + session.set("userId", userId); return redirect(redirectTo, { headers: { - 'Set-Cookie': await storage.commitSession(session), + "Set-Cookie": await storage.commitSession(session), }, - }) + }); } ``` + This function ... - ... creates a new session. @@ -834,6 +872,7 @@ export async function login({ email, password }: LoginForm) { + return createUserSession(user.id, "/"); } ``` + ### Handle the login and register form submissions You have created all of the functions needed to create new users and log them in. Now you will put those to use in the forms you built. @@ -845,11 +884,12 @@ In `app/routes/login.tsx`, export an [`action`](https://remix.run/docs/en/v1/tut // ... -import { ActionFunction } from '@remix-run/node' -export const action: ActionFunction = async ({ request }) => {} +import { ActionFunction } from "@remix-run/node"; +export const action: ActionFunction = async ({ request }) => {}; // ... ``` + > **Note**: Remix looks for an exported function named `action` to set up a POST request on the route you are defining. Now create a couple of validator functions in a new file inside of `app/utils` named `validators.server.ts` that will be used to validate the form input. @@ -860,62 +900,67 @@ Now create a couple of validator functions in a new file inside of `app/utils` n export const validateEmail = (email: string): string | undefined => { var validRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/; if (!email.length || !validRegex.test(email)) { - return "Please enter a valid email address" + return "Please enter a valid email address"; } -} +}; export const validatePassword = (password: string): string | undefined => { if (password.length < 5) { - return "Please enter a password that is at least 5 characters long" + return "Please enter a password that is at least 5 characters long"; } -} +}; export const validateName = (name: string): string | undefined => { - if (!name.length) return `Please enter a value` -} + if (!name.length) return `Please enter a value`; +}; ``` + Within the `action` function in `app/routes/login.tsx`, grab the form data from the request and validate it is of the correct format. ```tsx // app/routes/login.tsx // ... // Added the json function 👇 -import { ActionFunction, json } from '@remix-run/node' -import { validateEmail, validateName, validatePassword } from '~/utils/validators.server' +import { ActionFunction, json } from "@remix-run/node"; +import { validateEmail, validateName, validatePassword } from "~/utils/validators.server"; export const action: ActionFunction = async ({ request }) => { - const form = await request.formData() - const action = form.get('_action') - const email = form.get('email') - const password = form.get('password') - let firstName = form.get('firstName') - let lastName = form.get('lastName') - - if (typeof action !== 'string' || typeof email !== 'string' || typeof password !== 'string') { - return json({ error: `Invalid Form Data`, form: action }, { status: 400 }) + const form = await request.formData(); + const action = form.get("_action"); + const email = form.get("email"); + const password = form.get("password"); + let firstName = form.get("firstName"); + let lastName = form.get("lastName"); + + if (typeof action !== "string" || typeof email !== "string" || typeof password !== "string") { + return json({ error: `Invalid Form Data`, form: action }, { status: 400 }); } - if (action === 'register' && (typeof firstName !== 'string' || typeof lastName !== 'string')) { - return json({ error: `Invalid Form Data`, form: action }, { status: 400 }) + if (action === "register" && (typeof firstName !== "string" || typeof lastName !== "string")) { + return json({ error: `Invalid Form Data`, form: action }, { status: 400 }); } const errors = { email: validateEmail(email), password: validatePassword(password), - ...(action === 'register' + ...(action === "register" ? { - firstName: validateName((firstName as string) || ''), - lastName: validateName((lastName as string) || ''), + firstName: validateName((firstName as string) || ""), + lastName: validateName((lastName as string) || ""), } : {}), - } + }; if (Object.values(errors).some(Boolean)) - return json({ errors, fields: { email, password, firstName, lastName }, form: action }, { status: 400 }) -} + return json( + { errors, fields: { email, password, firstName, lastName }, form: action }, + { status: 400 }, + ); +}; // ... ``` + The code above may look a bit scary, but in a nutshell it ... - ... pulls the form data out of the request object. @@ -948,6 +993,7 @@ export const action: ActionFunction = async ({ request }) => { // ... ``` + The `switch` statement will allow you to conditionally run the `login` and `register` functions depending on what the `_action` value from the form contains. In order to actually trigger this action, the forms need to post to this route. Fortunately, Remix will take care of this, as it automatically configures `POST` requests to the `/login` route when it recognizes the exported `action` function. @@ -966,53 +1012,57 @@ In `app/utils/auth.server.ts` you will need to add a few helper functions. // app/utils/auth.server.ts // ... -export async function requireUserId(request: Request, redirectTo: string = new URL(request.url).pathname) { - const session = await getUserSession(request) - const userId = session.get('userId') - if (!userId || typeof userId !== 'string') { - const searchParams = new URLSearchParams([['redirectTo', redirectTo]]) - throw redirect(`/login?${searchParams}`) +export async function requireUserId( + request: Request, + redirectTo: string = new URL(request.url).pathname, +) { + const session = await getUserSession(request); + const userId = session.get("userId"); + if (!userId || typeof userId !== "string") { + const searchParams = new URLSearchParams([["redirectTo", redirectTo]]); + throw redirect(`/login?${searchParams}`); } - return userId + return userId; } function getUserSession(request: Request) { - return storage.getSession(request.headers.get('Cookie')) + return storage.getSession(request.headers.get("Cookie")); } async function getUserId(request: Request) { - const session = await getUserSession(request) - const userId = session.get('userId') - if (!userId || typeof userId !== 'string') return null - return userId + const session = await getUserSession(request); + const userId = session.get("userId"); + if (!userId || typeof userId !== "string") return null; + return userId; } export async function getUser(request: Request) { - const userId = await getUserId(request) - if (typeof userId !== 'string') { - return null + const userId = await getUserId(request); + if (typeof userId !== "string") { + return null; } try { const user = await prisma.user.findUnique({ where: { id: userId }, select: { id: true, email: true, profile: true }, - }) - return user + }); + return user; } catch { - throw logout(request) + throw logout(request); } } export async function logout(request: Request) { - const session = await getUserSession(request) - return redirect('/login', { + const session = await getUserSession(request); + return redirect("/login", { headers: { - 'Set-Cookie': await storage.destroySession(session), + "Set-Cookie": await storage.destroySession(session), }, - }) + }); } ``` + This is a lot of new functionality. Here is what the functions above will do: - `requireUserId` checks for a user's session. If one exists, it is a success and just returns the `userId`. If it fails, however, it will redirect the user to the login screen. @@ -1028,16 +1078,17 @@ In `app/routes/index.tsx`, return the user to the login screen if they are not l ```tsx // app/routes/index.tsx -import { LoaderFunction } from '@remix-run/node' -import { requireUserId } from '~/utils/auth.server' +import { LoaderFunction } from "@remix-run/node"; +import { requireUserId } from "~/utils/auth.server"; export const loader: LoaderFunction = async ({ request }) => { - await requireUserId(request) - return null -} + await requireUserId(request); + return null; +}; // ... ``` + > **Note**: Remix runs the [`loader`](https://remix.run/docs/en/v1/api/conventions#loader) function **before** serving your page. This means any redirects in a loader will trigger before your page can be served. If you try to navigate to the base route _(`/`)_ of your application while not logged in you should be redirected to the login screen with a `redirectTo` param in the URL. @@ -1049,15 +1100,16 @@ Next, do essentially the opposite. If a logged in user tries to get to the login ```tsx // app/routes/login.tsx // ... -import { ActionFunction, json, LoaderFunction, redirect } from '@remix-run/node' -import { login, register, getUser } from '~/utils/auth.server' +import { ActionFunction, json, LoaderFunction, redirect } from "@remix-run/node"; +import { login, register, getUser } from "~/utils/auth.server"; export const loader: LoaderFunction = async ({ request }) => { // If there's already a user in the session, redirect to the home page - return (await getUser(request)) ? redirect('/') : null -} + return (await getUser(request)) ? redirect("/") : null; +}; // ... ``` + ## Add form validation Great! Your sign in and sign up forms are working and you have set up authorization and redirects on your private routes. You're almost at the finish line! @@ -1107,6 +1159,7 @@ export function FormField({ } ``` + This component will now take in an error message. When the user starts to type in that field, if any error message was being shown it will be cleared out. In the login form you will need to get access to the data returned from the action using Remix's [`useActionData`](https://remix.run/docs/en/v1/api/remix#useactiondata) hook in order to pull out the error messages. @@ -1115,27 +1168,28 @@ In the login form you will need to get access to the data returned from the acti // app/routes/login.tsx // ... -import { useActionData } from '@remix-run/react' -import { useRef, useEffect } from 'react' +import { useActionData } from "@remix-run/react"; +import { useRef, useEffect } from "react"; // ... export default function Login() { // ... // 1 - const actionData = useActionData() + const actionData = useActionData(); // 2 - const firstLoad = useRef(true) - const [errors, setErrors] = useState(actionData?.errors || {}) - const [formError, setFormError] = useState(actionData?.error || '') + const firstLoad = useRef(true); + const [errors, setErrors] = useState(actionData?.errors || {}); + const [formError, setFormError] = useState(actionData?.error || ""); // 3 const [formData, setFormData] = useState({ - email: actionData?.fields?.email || '', - password: actionData?.fields?.password || '', - firstName: actionData?.fields?.lastName || '', - lastName: actionData?.fields?.firstName || '', - }) + email: actionData?.fields?.email || "", + password: actionData?.fields?.password || "", + firstName: actionData?.fields?.lastName || "", + lastName: actionData?.fields?.firstName || "", + }); // ... } ``` + This code adds the following: 1. Hooks into the data returned from the `action` function. @@ -1152,27 +1206,30 @@ export default function Login() { useEffect(() => { if (!firstLoad.current) { const newState = { - email: '', - password: '', - firstName: '', - lastName: '', - } - setErrors(newState) - setFormError('') - setFormData(newState) + email: "", + password: "", + firstName: "", + lastName: "", + }; + setErrors(newState); + setFormError(""); + setFormData(newState); } - }, [action]) + }, [action]); useEffect(() => { if (!firstLoad.current) { - setFormError('') + setFormError(""); } - }, [formData]) + }, [formData]); - useEffect(() => { firstLoad.current = false }, []) + useEffect(() => { + firstLoad.current = false; + }, []); } // ... ``` + With those in place, you can finally let your form and fields know which errors to display. ```tsx @@ -1222,6 +1279,7 @@ diff // app/routes/login.tsx // ... ``` + Now you should see error messages and form resets working properly on your sign up and sign in forms! ![](/fullstack-remix-prisma-mongodb-2-ZTmOy58p4re8/imgs/error-message.png) @@ -1237,4 +1295,3 @@ Now you should see error messages and form resets working properly on your sign - How to store and query your data using Prisma when creating and authenticating users. In the next section of this series you will build the home page of Kudos and the kudos-sharing functionality. You will also add searching and filtering capabilities to the kudos feed. - diff --git a/apps/blog/content/blog/fullstack-remix-prisma-mongodb-3-By5pmN5Nzo1v/index.mdx b/apps/blog/content/blog/fullstack-remix-prisma-mongodb-3-By5pmN5Nzo1v/index.mdx index a860656291..278b97bf18 100644 --- a/apps/blog/content/blog/fullstack-remix-prisma-mongodb-3-By5pmN5Nzo1v/index.mdx +++ b/apps/blog/content/blog/fullstack-remix-prisma-mongodb-3-By5pmN5Nzo1v/index.mdx @@ -79,18 +79,19 @@ This new file should export a function component called `Home` for now, along wi ```tsx // app/routes/home.tsx -import { LoaderFunction } from '@remix-run/node' -import { requireUserId } from '~/utils/auth.server' +import { LoaderFunction } from "@remix-run/node"; +import { requireUserId } from "~/utils/auth.server"; export const loader: LoaderFunction = async ({ request }) => { - await requireUserId(request) - return null -} + await requireUserId(request); + return null; +}; export default function Home() { - return

Home Page

+ return

Home Page

; } ``` + This `/home` route will act as the main page of your application rather than the base url. Currently, the `app/routes/index.tsx` file _(the `/` route)_ renders a React component. That route should only ever redirect a user: either to the `/home` or `/login` route. Set up a [resource route](https://remix.run/docs/en/v1/guides/resource-routes) in its place to achieve that functionality. @@ -104,14 +105,15 @@ Delete the existing `app/routes/index.tsx` file and replace it with an `index.ts ```ts // app/routes/index.ts -import { LoaderFunction, redirect } from '@remix-run/node' -import { requireUserId } from '~/utils/auth.server' +import { LoaderFunction, redirect } from "@remix-run/node"; +import { requireUserId } from "~/utils/auth.server"; export const loader: LoaderFunction = async ({ request }) => { - await requireUserId(request) - return redirect('/home') -} + await requireUserId(request); + return redirect("/home"); +}; ``` + > **Note**: The file's extension was changed to `.ts` because this route will never render a component. The `loader` above will first check if a user is logged in when they hit the `/` route. The `requireUserId` function will redirect to `/login` if there isn't a valid session. @@ -146,24 +148,25 @@ export function UserPanel() {
- ) + ); } ``` + This creates the side panel that will contain the list of users. The component is _static_ though, meaning it does not perform any actions or vary in any way. Before making this component more _dynamic_ by adding a list of users, import it into the `app/routes/home.tsx` page and render it onto the page. ```tsx // app/routes/home.tsx -import { LoaderFunction } from '@remix-run/node' -import { requireUserId } from '~/utils/auth.server' -import { Layout } from '~/components/layout' -import { UserPanel } from '~/components/user-panel' +import { LoaderFunction } from "@remix-run/node"; +import { requireUserId } from "~/utils/auth.server"; +import { Layout } from "~/components/layout"; +import { UserPanel } from "~/components/user-panel"; export const loader: LoaderFunction = async ({ request }) => { - await requireUserId(request) - return null // <- A loader always has to return some value, even if that is null -} + await requireUserId(request); + return null; // <- A loader always has to return some value, even if that is null +}; export default function Home() { return ( @@ -172,9 +175,10 @@ export default function Home() {

- ) + ); } ``` + The code above imports the new component and the `Layout` component, then renders the new component within the layout. ![](/fullstack-remix-prisma-mongodb-3-By5pmN5Nzo1v/imgs/user-panel.png) @@ -197,12 +201,13 @@ export const getOtherUsers = async (userId: string) => { }, orderBy: { profile: { - firstName: 'asc', + firstName: "asc", }, }, - }) -} + }); +}; ``` + The `where` filter excludes any documents whose `id` matches the `userId` parameter. This will be used to grab every `user` _except the currently logged in user_. > **Note**: Notice how easy it is to sort by fields within an embedded document? @@ -231,6 +236,7 @@ export const loader: LoaderFunction = async ({ request }) => { // ... ``` + > **Note**: Any code that is run within a [`loader`](https://remix.run/docs/en/v1/guides/data-loading) function is not exposed to the client-side code. You can thank Remix for this awesome feature! If you had any users in your database and outputted the `users` variable inside of the loader, you should see a list of all users _except yourself_. @@ -247,12 +253,13 @@ Set up a new `users` prop in the `UserPanel` component. ```tsx // app/components/user-panel.tsx -import { User } from '@prisma/client' +import { User } from "@prisma/client"; export function UserPanel({ users }: { users: User[] }) { // ... } ``` + The `User` type used here was generated by Prisma and is available via Prisma Client. Remix works very nicely with Prisma because it is extremely easy to achieve end-to-end type safety in a fullstack framework. > **Note**: End-to-end type safety occurs when the types across your entire stack are kept in sync as the shape of your data changes. @@ -261,10 +268,10 @@ In `app/routes/home.tsx` you may now supply the users to the `UserPanel` compone ```tsx // app/routes/home.tsx -import { useLoaderData } from '@remix-run/react' +import { useLoaderData } from "@remix-run/react"; // ... export default function Home() { - const { users } = useLoaderData() + const { users } = useLoaderData(); return (
@@ -272,10 +279,11 @@ export default function Home() {
- ) + ); } // ... ``` + The component will now have the `users` to work with. Now it needs to display them. ## Build the user display component @@ -286,12 +294,12 @@ Create a new file in `app/components` named `user-circle.tsx` and add the follow ```tsx // app/components/user-circle.tsx -import { Profile } from '@prisma/client' +import { Profile } from "@prisma/client"; interface props { - profile: Profile - className?: string - onClick?: (...args: any) => any + profile: Profile; + className?: string; + onClick?: (...args: any) => any; } export function UserCircle({ profile, onClick, className }: props) { @@ -305,9 +313,10 @@ export function UserCircle({ profile, onClick, className }: props) { {profile.lastName.charAt(0).toUpperCase()}
- ) + ); } ``` + This component uses the `Profile` type generated by Prisma because you will be passing in only the `profile` data from the `user` documents. It also has some configurable options that allow you to provide a click action and add additional classes to customize its style. @@ -330,6 +339,7 @@ export function UserPanel({ users }: { users: User[] }) { ) } ``` + Beautiful! Your users will now be rendered in a nice column on the left side of the home page. The only non-functional piece of the side panel at this point is the sign out button. ![](/fullstack-remix-prisma-mongodb-3-By5pmN5Nzo1v/imgs/user-list.png) @@ -347,6 +357,7 @@ import { logout } from "~/utils/auth.server"; export const action: ActionFunction = async ({ request }) => logout(request); export const loader: LoaderFunction = async () => redirect("/"); ``` + This route handles two possible actions: POST and GET - `POST`: This will trigger the `logout` function written in the previous part of this series. @@ -374,6 +385,7 @@ export function UserPanel({ users }: props) { ) } ``` + Your users can now sign out of the application! The user whose session is associated with the `POST` request will be signed out and their session destroyed. ## Add the ability to send kudos @@ -396,7 +408,6 @@ There are a couple of data points you will be saving and displaying that are not 2. Add a 1:n relation in the `User` model that defines the kudos a user is the _author_ of. Also add a similar relation that defines the kudos a user is a _recipient_ of. 3. Add `enum`s for emojis, departments, and colors to define the available options. - ```prisma // prisma/schema.prisma @@ -436,6 +447,7 @@ model Kudo { style KudoStyle? } ``` + ```prisma diff // prisma/schema.prisma @@ -453,6 +465,7 @@ model Kudo { + recipientId String @db.ObjectId } ``` + > **Note:** After applying `@default` to a field, if a record in your collection does not have the new required field if will be updated to include that field with the default value the next time it is read. That's all you'll need to update for now. Run `npx prisma db push`, which will automatically re-generate `PrismaClient`. @@ -480,22 +493,23 @@ In that new file export a `loader` function and a React component that renders s ```tsx // app/routes/home/kudo.$userId.tsx -import { json, LoaderFunction } from '@remix-run/node' -import { useLoaderData } from '@remix-run/react' +import { json, LoaderFunction } from "@remix-run/node"; +import { useLoaderData } from "@remix-run/react"; // 1 export const loader: LoaderFunction = async ({ request, params }) => { // 2 - const { userId } = params - return json({ userId }) -} + const { userId } = params; + return json({ userId }); +}; export default function KudoModal() { // 3 - const data = useLoaderData() - return

User: {data.userId}

+ const data = useLoaderData(); + return

User: {data.userId}

; } ``` + The code above does a few things: 1. It pulls the `params` field from the loader function. @@ -524,6 +538,7 @@ export default function Home() { } ``` + If you head over to [http://localhost:3000/home/kudo/123](http://localhost:3000/home/kudo/123), you should now see the text "User: 123" displayed at the very top of the page. If you change the value in the URL to something other than `123` you should see that change reflected on the screen. ![](/fullstack-remix-prisma-mongodb-3-By5pmN5Nzo1v/imgs/nested-route.png) @@ -542,9 +557,10 @@ export const getUserById = async (userId: string) => { where: { id: userId, }, - }) -} + }); +}; ``` + The query above finds the unique record in the database with the given `id`. The [`findUnique`](https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#findunique) function allows you to filter your query using _uniquely identifying_ fields, or fields with values that _must_ be unique to that record within your database. Next: @@ -554,22 +570,23 @@ Next: ```tsx // app/routes/home/kudo.$userId.tsx -import { json, LoaderFunction, redirect } from '@remix-run/node' -import { useLoaderData } from '@remix-run/react' -import { getUserById } from '~/utils/user.server' +import { json, LoaderFunction, redirect } from "@remix-run/node"; +import { useLoaderData } from "@remix-run/react"; +import { getUserById } from "~/utils/user.server"; export const loader: LoaderFunction = async ({ request, params }) => { - const { userId } = params + const { userId } = params; - if (typeof userId !== 'string') { - return redirect('/home') + if (typeof userId !== "string") { + return redirect("/home"); } - const recipient = await getUserById(userId) - return json({ recipient }) -} + const recipient = await getUserById(userId); + return json({ recipient }); +}; // ... ``` + Next, you need a way to navigate to a nested route with a valid `id`. In `app/components/user-panel.tsx`, the file where you are rendering the user list, import the `useNavigation` hook Remix provides and use it to navigate to the nested route when a user is clicked. @@ -596,6 +613,7 @@ export function UserPanel({ users }: props) { ) } ``` + Now when your users click on another user in that panel, they will be navigated to a sub-route with that user's information. ![](/fullstack-remix-prisma-mongodb-3-By5pmN5Nzo1v/imgs/nested-route-names.gif) @@ -615,51 +633,52 @@ In `app/components` create a new file named `portal.tsx` with the following cont ```tsx // app/components/portal.tsx -import { createPortal } from 'react-dom' -import { useState, useEffect } from 'react' +import { createPortal } from "react-dom"; +import { useState, useEffect } from "react"; interface props { - children: React.ReactNode - wrapperId: string + children: React.ReactNode; + wrapperId: string; } // 1 const createWrapper = (wrapperId: string) => { - const wrapper = document.createElement('div') - wrapper.setAttribute('id', wrapperId) - document.body.appendChild(wrapper) - return wrapper -} + const wrapper = document.createElement("div"); + wrapper.setAttribute("id", wrapperId); + document.body.appendChild(wrapper); + return wrapper; +}; export const Portal: React.FC = ({ children, wrapperId }) => { - const [wrapper, setWrapper] = useState(null) + const [wrapper, setWrapper] = useState(null); useEffect(() => { // 2 - let element = document.getElementById(wrapperId) - let created = false + let element = document.getElementById(wrapperId); + let created = false; if (!element) { - created = true - element = createWrapper(wrapperId) + created = true; + element = createWrapper(wrapperId); } - setWrapper(element) + setWrapper(element); // 3 return () => { if (created && element?.parentNode) { - element.parentNode.removeChild(element) + element.parentNode.removeChild(element); } - } - }, [wrapperId]) + }; + }, [wrapperId]); - if (wrapper === null) return null + if (wrapper === null) return null; // 4 - return createPortal(children, wrapper) -} + return createPortal(children, wrapper); +}; ``` + Here's an explanation of what is going on in this component: 1. A function is defined that generates a `div` with an `id`. That element is then attached to the document's `body`. @@ -686,6 +705,7 @@ export default function KudoModal() { + return {/* ... */} } ``` + If you navigate to your nested route, you will see a `div` with an `id` of `"kudo-modal"` is now rendered as a direct child of the `body` rather than where the nested route is being rendered in the DOM tree. ![](/fullstack-remix-prisma-mongodb-3-By5pmN5Nzo1v/imgs/portal.gif) @@ -705,39 +725,42 @@ Add the following code to create the `Modal` component: ```tsx // app/components/modal.tsx -import { Portal } from './portal' -import { useNavigate } from '@remix-run/react' +import { Portal } from "./portal"; +import { useNavigate } from "@remix-run/react"; interface props { - children: React.ReactNode - isOpen: boolean - ariaLabel?: string - className?: string + children: React.ReactNode; + isOpen: boolean; + ariaLabel?: string; + className?: string; } export const Modal: React.FC = ({ children, isOpen, ariaLabel, className }) => { - const navigate = useNavigate() - if (!isOpen) return null + const navigate = useNavigate(); + if (!isOpen) return null; return (
navigate('/home')} + onClick={() => navigate("/home")} >
-
+
{/* This is where the modal content is rendered */} {children}
- ) -} + ); +}; ``` + The `Portal` component is imported and wraps the entirety of the modal to ensure it is rendered in a safe location. The modal is then defined as a fixed element on the screen with an opaque backdrop using various TailwindCSS helpers. @@ -767,6 +790,7 @@ export default function KudoModal() { ) } ``` + The modal should now open up when a user from the side panel is clicked. ![](/fullstack-remix-prisma-mongodb-3-By5pmN5Nzo1v/imgs/working-modal.gif) @@ -786,49 +810,49 @@ export const loader: LoaderFunction = async ({ request, params }) => { } // ... ``` + Then make the following changes to the `KudoModal` function in that file: ```tsx // app/routes/home/kudo.$userId.tsx // 1 -import { - useLoaderData, - useActionData -} from '@remix-run/react' -import { UserCircle } from '~/components/user-circle' -import { useState } from 'react' -import { KudoStyle } from '@prisma/client' +import { useLoaderData, useActionData } from "@remix-run/react"; +import { UserCircle } from "~/components/user-circle"; +import { useState } from "react"; +import { KudoStyle } from "@prisma/client"; // ... export default function KudoModal() { -// 2 -const actionData = useActionData() -const [formError] = useState(actionData?.error || '') -const [formData, setFormData] = useState({ - message: '', - style: { - backgroundColor: 'RED', - textColor: 'WHITE', - emoji: 'THUMBSUP', - } as KudoStyle, -}) + // 2 + const actionData = useActionData(); + const [formError] = useState(actionData?.error || ""); + const [formData, setFormData] = useState({ + message: "", + style: { + backgroundColor: "RED", + textColor: "WHITE", + emoji: "THUMBSUP", + } as KudoStyle, + }); // 3 -const handleChange = (e: React.ChangeEvent, field: string) => { - setFormData(data => ({ ...data, [field]: e.target.value })) -} + const handleChange = ( + e: React.ChangeEvent, + field: string, + ) => { + setFormData((data) => ({ ...data, [field]: e.target.value })); + }; - const { - recipient, - user - } = useLoaderData() + const { recipient, user } = useLoaderData(); // 4 return ( -
{formError}
+
+ {formError} +
@@ -839,7 +863,8 @@ const handleChange = (e: React.ChangeEvent {recipient.profile.department && ( - {recipient.profile.department[0].toUpperCase() + recipient.profile.department.toLowerCase().slice(1)} + {recipient.profile.department[0].toUpperCase() + + recipient.profile.department.toLowerCase().slice(1)} )}
@@ -848,7 +873,7 @@ const handleChange = (e: React.ChangeEvent handleChange(e, 'message')} + onChange={(e) => handleChange(e, "message")} placeholder={`Say something nice about ${recipient.profile.firstName}...`} />
@@ -870,9 +895,10 @@ const handleChange = (e: React.ChangeEvent - ) + ); } ``` + This was a big chunk of new code, so take a look at what changes were made: 1. Imports a few components and hooks you will need. @@ -891,23 +917,23 @@ Create a new file in `app/components` named `select-box.tsx` that exports a `Sel interface props { options: { - name: string - value: any - }[] - className?: string - containerClassName?: string - id?: string - name?: string - label?: string - value?: any - onChange?: (...args: any) => any + name: string; + value: any; + }[]; + className?: string; + containerClassName?: string; + id?: string; + name?: string; + label?: string; + value?: any; + onChange?: (...args: any) => any; } export function SelectBox({ options = [], onChange = () => {}, - className = '', - containerClassName = '', + className = "", + containerClassName = "", name, id, value, @@ -919,8 +945,14 @@ export function SelectBox({ {label}
- + {options.map((option) => ( @@ -937,9 +969,10 @@ export function SelectBox({
- ) + ); } ``` + This component is similar to the `FormField` component in that it is a _controlled component_ that takes in some configuration and allows its state to be managed by its parent. These select boxes will need to be populated with the color and emoji options. Create a helper file to hold the possible options at `app/utils/constants.ts`: @@ -948,27 +981,28 @@ These select boxes will need to be populated with the color and emoji options. C // app/utils/constants.ts export const colorMap = { - RED: 'text-red-400', - GREEN: 'text-green-400', - BLUE: 'text-blue-400', - WHITE: 'text-white', - YELLOW: 'text-yellow-300', -} + RED: "text-red-400", + GREEN: "text-green-400", + BLUE: "text-blue-400", + WHITE: "text-white", + YELLOW: "text-yellow-300", +}; export const backgroundColorMap = { - RED: 'bg-red-400', - GREEN: 'bg-green-400', - BLUE: 'bg-blue-400', - WHITE: 'bg-white', - YELLOW: 'bg-yellow-300', -} + RED: "bg-red-400", + GREEN: "bg-green-400", + BLUE: "bg-blue-400", + WHITE: "bg-white", + YELLOW: "bg-yellow-300", +}; export const emojiMap = { - THUMBSUP: '👍', - PARTY: '🎉', - HANDSUP: '🙌🏻', -} + THUMBSUP: "👍", + PARTY: "🎉", + HANDSUP: "🙌🏻", +}; ``` + Now in `app/routes/home/kudo.$userId.tsx`, import the `SelectBox` component and the constants. Also add the variables and functions requried to hook them up to the form's state and render the `SelectBox` components in place of the `{/* Select Boxes Go Here */}` comment: ```tsx @@ -1043,6 +1077,7 @@ export default function KudoModal() { ) } ``` + The select boxes will now appear with all of the possible options. ![](/fullstack-remix-prisma-mongodb-3-By5pmN5Nzo1v/imgs/select-boxes.png) @@ -1056,33 +1091,40 @@ Create a new file at `app/components` named `kudo.tsx`: ```tsx // app/components/kudo.tsx -import { UserCircle } from '~/components/user-circle' -import { Profile, Kudo as IKudo } from '@prisma/client' -import { colorMap, backgroundColorMap, emojiMap } from '~/utils/constants' +import { UserCircle } from "~/components/user-circle"; +import { Profile, Kudo as IKudo } from "@prisma/client"; +import { colorMap, backgroundColorMap, emojiMap } from "~/utils/constants"; export function Kudo({ profile, kudo }: { profile: Profile; kudo: Partial }) { return (
-

+

{profile.firstName} {profile.lastName}

-

{kudo.message}

+

+ {kudo.message} +

- {emojiMap[kudo.style?.emoji || 'THUMBSUP']} + {emojiMap[kudo.style?.emoji || "THUMBSUP"]}
- ) + ); } ``` + This component takes in the props: - `profile`: The `profile` data from the recipients `user` document. @@ -1115,6 +1157,7 @@ export default function KudoModal() { ) } ``` + The preview will now be rendered, displaying the currently logged in user's information and the styled message they are going to send. ![](/fullstack-remix-prisma-mongodb-3-By5pmN5Nzo1v/imgs/kudo-preview.gif) @@ -1130,10 +1173,15 @@ In this file, export a `createKudo` method that takes in the kudo form data, the ```ts // app/utils/kudos.server.ts -import { prisma } from './prisma.server' -import { KudoStyle } from '@prisma/client' +import { prisma } from "./prisma.server"; +import { KudoStyle } from "@prisma/client"; -export const createKudo = async (message: string, userId: string, recipientId: string, style: KudoStyle) => { +export const createKudo = async ( + message: string, + userId: string, + recipientId: string, + style: KudoStyle, +) => { await prisma.kudo.create({ data: { // 1 @@ -1151,9 +1199,10 @@ export const createKudo = async (message: string, userId: string, recipientId: s }, }, }, - }) -} + }); +}; ``` + The query above does the following: 1. Passes in the `message` string and `style` embedded document. @@ -1225,6 +1274,7 @@ import { // ... ``` + Here's an overview of the snippet above: 1. Imports the new `createKudo` function, along with a few types generated by Prisma, the `ActionFunction` type from Remix, and the `requireUserId` function you wrote previously. @@ -1245,7 +1295,7 @@ In `app/utils/kudos.server.ts` create and export a new function named `getFilter // app/utils/kudos.server.ts // 👇 Added the Prisma namespace in the import -import { KudoStyle, Prisma } from '@prisma/client' +import { KudoStyle, Prisma } from "@prisma/client"; // ... @@ -1272,9 +1322,10 @@ export const getFilteredKudos = async ( recipientId: userId, ...whereFilter, }, - }) -} + }); +}; ``` + The function above takes in a few different parameters. Here is what those are: - `userId`: The `id` of the user whose kudos the query should retrieve. @@ -1287,24 +1338,24 @@ Now in `app/routes/home.tsx`, import that function and invoke it in the `loader` ```tsx // app/routes/home.tsx -import { getFilteredKudos } from '~/utils/kudos.server' -import { Kudo } from '~/components/kudo' -import { Kudo as IKudo, Profile } from '@prisma/client' +import { getFilteredKudos } from "~/utils/kudos.server"; +import { Kudo } from "~/components/kudo"; +import { Kudo as IKudo, Profile } from "@prisma/client"; interface KudoWithProfile extends IKudo { author: { - profile: Profile - } + profile: Profile; + }; } export const loader: LoaderFunction = async ({ request }) => { // ... - const kudos = await getFilteredKudos(userId, {}, {}) - return json({ users, kudos }) -} + const kudos = await getFilteredKudos(userId, {}, {}); + return json({ users, kudos }); +}; export default function Home() { - const { users, kudos } = useLoaderData() + const { users, kudos } = useLoaderData(); return ( @@ -1323,9 +1374,10 @@ export default function Home() {
- ) + ); } ``` + The `Kudo` and `Profile` types generated by Prisma are combined to create a `KudoWithProfile` type. This is needed because your array has kudos that include the profile data from the author. If you send a couple of kudos to an account and log in to that account, you should now see a rendered list of kudos on your feed. @@ -1341,16 +1393,16 @@ Create a new file in `app/components` named `search-bar.tsx`. This component wil ```tsx // app/components/search-bar.tsx -import { useNavigate, useSearchParams } from '@remix-run/react' +import { useNavigate, useSearchParams } from "@remix-run/react"; export function SearchBar() { - const navigate = useNavigate() - let [searchParams] = useSearchParams() + const navigate = useNavigate(); + let [searchParams] = useSearchParams(); const clearFilters = () => { - searchParams.delete('filter') - navigate('/home') - } + searchParams.delete("filter"); + navigate("/home"); + }; return (
@@ -1376,7 +1428,7 @@ export function SearchBar() { > Search - {searchParams.get('filter') && ( + {searchParams.get("filter") && (
// [!code ++] - // [!code ++] -

Already have an account? Sign in here.

// [!code ++] + + // [!code ++] +

Already have an account? Sign in here.

+ // [!code ++] @@ -602,17 +599,14 @@ export const prerender = false;

Sign In

-
// [!code ++] - // [!code ++] - // [!code ++] + + // [!code ++] // [!code ++] + // [!code ++] // [!code ++] -
// [!code ++] -

Don't have an account? Sign up here.

// [!code ++] + + // [!code ++] +

Don't have an account? Sign up here.

+ // [!code ++]
diff --git a/apps/docs/content/docs/guides/database/multiple-databases.mdx b/apps/docs/content/docs/guides/database/multiple-databases.mdx index 1dc7745b33..0a3e09f3b3 100644 --- a/apps/docs/content/docs/guides/database/multiple-databases.mdx +++ b/apps/docs/content/docs/guides/database/multiple-databases.mdx @@ -1,10 +1,10 @@ --- title: Multiple databases -description: 'Learn how to use multiple Prisma Clients in a single app to connect to multiple databases, handle migrations, and deploy your application to Vercel' +description: "Learn how to use multiple Prisma Clients in a single app to connect to multiple databases, handle migrations, and deploy your application to Vercel" image: /img/guides/multiple-databases.png url: /guides/database/multiple-databases metaTitle: How to use Prisma ORM with multiple databases in a single app -metaDescription: 'Learn how to use multiple Prisma Clients in a single app to connect to multiple databases, handle migrations, and deploy your application to Vercel.' +metaDescription: "Learn how to use multiple Prisma Clients in a single app to connect to multiple databases, handle migrations, and deploy your application to Vercel." --- ## Introduction diff --git a/apps/docs/content/docs/guides/deployment/bun-workspaces.mdx b/apps/docs/content/docs/guides/deployment/bun-workspaces.mdx index 948f57237b..0bbcfa68c1 100644 --- a/apps/docs/content/docs/guides/deployment/bun-workspaces.mdx +++ b/apps/docs/content/docs/guides/deployment/bun-workspaces.mdx @@ -88,7 +88,6 @@ This command: - Creates a `prisma.config.ts` file for configuring Prisma. - Creates a `.env` file with a local `DATABASE_URL`. - Create a Prisma Postgres database and replace the generated `DATABASE_URL` in your `.env` file with the `postgres://...` connection string from the CLI output: ```bash @@ -124,7 +123,8 @@ Add a `scripts` section to your database `package.json` (Bun init may not add on ```json title="database/package.json" { - "scripts": { // [!code ++] + "scripts": { + // [!code ++] "db:generate": "prisma generate", // [!code ++] "db:migrate": "prisma migrate dev", // [!code ++] "db:deploy": "prisma migrate deploy", // [!code ++] diff --git a/apps/docs/content/docs/guides/deployment/pnpm-workspaces.mdx b/apps/docs/content/docs/guides/deployment/pnpm-workspaces.mdx index 9e3698501e..6289d1d3fb 100644 --- a/apps/docs/content/docs/guides/deployment/pnpm-workspaces.mdx +++ b/apps/docs/content/docs/guides/deployment/pnpm-workspaces.mdx @@ -121,7 +121,6 @@ This command: - Creates a `prisma` directory containing a `schema.prisma` file for your database models. - Creates a `.env` file with a local `DATABASE_URL`. - Create a Prisma Postgres database and replace the generated `DATABASE_URL` in your `.env` file with the `postgres://...` connection string from the CLI output: ```bash diff --git a/apps/docs/content/docs/guides/deployment/turborepo.mdx b/apps/docs/content/docs/guides/deployment/turborepo.mdx index a0181517ac..118b4aa17d 100644 --- a/apps/docs/content/docs/guides/deployment/turborepo.mdx +++ b/apps/docs/content/docs/guides/deployment/turborepo.mdx @@ -1,10 +1,10 @@ --- title: Turborepo -description: 'Learn step-by-step how to integrate Prisma ORM with Turborepo to build modular, scalable monorepo architectures efficiently' +description: "Learn step-by-step how to integrate Prisma ORM with Turborepo to build modular, scalable monorepo architectures efficiently" image: /img/guides/prisma-turborepo-setup.png url: /guides/deployment/turborepo metaTitle: How to use Prisma ORM and Prisma Postgres with Turborepo -metaDescription: 'Learn step-by-step how to integrate Prisma ORM with Turborepo to build modular, scalable monorepo architectures efficiently.' +metaDescription: "Learn step-by-step how to integrate Prisma ORM with Turborepo to build modular, scalable monorepo architectures efficiently." --- Prisma is a powerful ORM for managing databases, and [Turborepo](https://turborepo.dev/docs) simplifies monorepo workflows. By combining these tools, you can create a scalable, modular architecture for your projects. @@ -209,13 +209,16 @@ Let's also add these scripts to `turbo.json` in the root and ensure that `DATABA "cache": false, "persistent": true }, - "db:generate": { // [!code ++] + "db:generate": { + // [!code ++] "cache": false // [!code ++] }, // [!code ++] - "db:migrate": { // [!code ++] + "db:migrate": { + // [!code ++] "cache": false // [!code ++] }, // [!code ++] - "db:deploy": { // [!code ++] + "db:deploy": { + // [!code ++] "cache": false // [!code ++] } // [!code ++] } diff --git a/apps/docs/content/docs/guides/frameworks/hono.mdx b/apps/docs/content/docs/guides/frameworks/hono.mdx index 809d2632ab..3f3f2d4c49 100644 --- a/apps/docs/content/docs/guides/frameworks/hono.mdx +++ b/apps/docs/content/docs/guides/frameworks/hono.mdx @@ -41,7 +41,7 @@ npm create hono@latest - _Which template do you want to use?_ `nodejs` - _Install dependencies? (recommended)_ `Yes` - _Which package manager do you want to use?_ `npm` -::: + ::: ## 2. Install and configure Prisma diff --git a/apps/docs/content/docs/guides/frameworks/meta.json b/apps/docs/content/docs/guides/frameworks/meta.json index d0a5178b5d..42a15bda7b 100644 --- a/apps/docs/content/docs/guides/frameworks/meta.json +++ b/apps/docs/content/docs/guides/frameworks/meta.json @@ -1 +1,16 @@ -{"title":"Frameworks","defaultOpen":true,"pages":["nextjs","astro","nuxt","sveltekit","solid-start","react-router-7","tanstack-start","nestjs","hono","elysia"]} +{ + "title": "Frameworks", + "defaultOpen": true, + "pages": [ + "nextjs", + "astro", + "nuxt", + "sveltekit", + "solid-start", + "react-router-7", + "tanstack-start", + "nestjs", + "hono", + "elysia" + ] +} diff --git a/apps/docs/content/docs/guides/frameworks/react-router-7.mdx b/apps/docs/content/docs/guides/frameworks/react-router-7.mdx index 8bc7f56270..ce32bb00d3 100644 --- a/apps/docs/content/docs/guides/frameworks/react-router-7.mdx +++ b/apps/docs/content/docs/guides/frameworks/react-router-7.mdx @@ -31,7 +31,7 @@ You'll be prompted to select the following, select `Yes` for both: - _Initialize a new git repository?_ `Yes` - _Install dependencies with npm?_ `Yes` -::: + ::: Now, navigate to the project directory: diff --git a/apps/docs/content/docs/guides/integrations/ai-sdk.mdx b/apps/docs/content/docs/guides/integrations/ai-sdk.mdx index 3bd3af5184..5963708f4b 100644 --- a/apps/docs/content/docs/guides/integrations/ai-sdk.mdx +++ b/apps/docs/content/docs/guides/integrations/ai-sdk.mdx @@ -1,10 +1,10 @@ --- title: AI SDK (with Next.js) -description: 'Build a chat application with AI SDK, Prisma, and Next.js to store chat sessions and messages' +description: "Build a chat application with AI SDK, Prisma, and Next.js to store chat sessions and messages" image: /img/guides/prisma-ai-sdk-nextjs-cover.png url: /guides/integrations/ai-sdk -metaTitle: 'How to use AI SDK with Prisma ORM, Prisma Postgres, and Next.js for chat applications' -metaDescription: 'Build a chat application with AI SDK, Prisma, and Next.js to store chat sessions and messages' +metaTitle: "How to use AI SDK with Prisma ORM, Prisma Postgres, and Next.js for chat applications" +metaDescription: "Build a chat application with AI SDK, Prisma, and Next.js to store chat sessions and messages" --- ## Introduction diff --git a/apps/docs/content/docs/guides/integrations/datadog.mdx b/apps/docs/content/docs/guides/integrations/datadog.mdx index ac26131e60..0f2249337d 100644 --- a/apps/docs/content/docs/guides/integrations/datadog.mdx +++ b/apps/docs/content/docs/guides/integrations/datadog.mdx @@ -1,10 +1,10 @@ --- title: Datadog -description: 'Learn how to configure Datadog tracing for a Prisma ORM project. Capture spans for every query using the @prisma/instrumentation package, dd-trace, and view them in Datadog' +description: "Learn how to configure Datadog tracing for a Prisma ORM project. Capture spans for every query using the @prisma/instrumentation package, dd-trace, and view them in Datadog" image: /img/guides/datadog-tracing-prisma.png url: /guides/integrations/datadog metaTitle: How to set up Datadog tracing with Prisma ORM and Prisma Postgres -metaDescription: 'Learn how to configure Datadog tracing for a Prisma ORM project. Capture spans for every query using the @prisma/instrumentation package, dd-trace, and view them in Datadog.' +metaDescription: "Learn how to configure Datadog tracing for a Prisma ORM project. Capture spans for every query using the @prisma/instrumentation package, dd-trace, and view them in Datadog." --- ## Introduction diff --git a/apps/docs/content/docs/guides/integrations/embed-studio.mdx b/apps/docs/content/docs/guides/integrations/embed-studio.mdx index dc91bef7ba..bcab8a5928 100644 --- a/apps/docs/content/docs/guides/integrations/embed-studio.mdx +++ b/apps/docs/content/docs/guides/integrations/embed-studio.mdx @@ -520,7 +520,6 @@ If you are using SQLite or MySQL, you can still embed Studio, but your `/api/stu To do this, create a new folder called `api` inside the `app` directory. Inside it, add a `studio` folder with a `route.ts` file. This file will handle all requests sent to `/api/studio` and act as the bridge between the Studio component in your frontend and the database in your backend: - ```typescript title="app/api/studio/route.ts" tab="PostgreSQL" import "dotenv/config"; import { createPrismaPostgresHttpClient } from "@prisma/studio-core/data/ppg"; diff --git a/apps/docs/content/docs/guides/integrations/permit-io.mdx b/apps/docs/content/docs/guides/integrations/permit-io.mdx index 68e6fade04..c5c072e0e2 100644 --- a/apps/docs/content/docs/guides/integrations/permit-io.mdx +++ b/apps/docs/content/docs/guides/integrations/permit-io.mdx @@ -640,7 +640,6 @@ If everything is set up correctly, the console will display: 🔐 ReBAC filtering is now active ``` - ### 9.3 Test your API You can simulate requests as different users by setting the `x-user-email` header. This mimics logged-in users with access to specific projects. diff --git a/apps/docs/content/docs/guides/integrations/pgfence.mdx b/apps/docs/content/docs/guides/integrations/pgfence.mdx index f751a31e5b..a7dc6c0518 100644 --- a/apps/docs/content/docs/guides/integrations/pgfence.mdx +++ b/apps/docs/content/docs/guides/integrations/pgfence.mdx @@ -49,12 +49,12 @@ pgfence parses every SQL statement and reports the lock mode, risk level, and an pgfence assigns a risk level to each statement based on the PostgreSQL lock it acquires: -| Risk level | Meaning | -|------------|---------| -| **LOW** | Safe operations with minimal locking (e.g., `ADD COLUMN` with a constant default on PG 11+) | -| **MEDIUM** | Operations that block writes but not reads (e.g., `CREATE INDEX` without `CONCURRENTLY`) | -| **HIGH** | Operations that block writes and competing DDL, but not plain reads (e.g., `ADD FOREIGN KEY` without `NOT VALID`) | -| **CRITICAL** | Operations that take `ACCESS EXCLUSIVE` locks on large tables (e.g., `DROP TABLE`, `TRUNCATE`) | +| Risk level | Meaning | +| ------------ | ----------------------------------------------------------------------------------------------------------------- | +| **LOW** | Safe operations with minimal locking (e.g., `ADD COLUMN` with a constant default on PG 11+) | +| **MEDIUM** | Operations that block writes but not reads (e.g., `CREATE INDEX` without `CONCURRENTLY`) | +| **HIGH** | Operations that block writes and competing DDL, but not plain reads (e.g., `ADD FOREIGN KEY` without `NOT VALID`) | +| **CRITICAL** | Operations that take `ACCESS EXCLUSIVE` locks on large tables (e.g., `DROP TABLE`, `TRUNCATE`) | Here is an example of pgfence analyzing a migration that adds an index without `CONCURRENTLY`: diff --git a/apps/docs/content/docs/guides/integrations/shopify.mdx b/apps/docs/content/docs/guides/integrations/shopify.mdx index c1d614679c..fcdfa36353 100644 --- a/apps/docs/content/docs/guides/integrations/shopify.mdx +++ b/apps/docs/content/docs/guides/integrations/shopify.mdx @@ -162,23 +162,23 @@ npx create-db Copy the connection string from the CLI output. It should look similar to this: - ```text - DATABASE_URL="postgresql://user:password@host:5432/database?sslmode=require" - ``` +```text +DATABASE_URL="postgresql://user:password@host:5432/database?sslmode=require" +``` Replace the generated `DATABASE_URL` in your `.env` file with the value from `npx create-db`. Apply your database schema: - ```npm - npx prisma migrate dev --name init - ``` +```npm +npx prisma migrate dev --name init +``` Then generate Prisma Client: - ```npm - npx prisma generate - ``` +```npm +npx prisma generate +``` Now, before moving on, let's update your `db.server.ts` file to use the newly generated Prisma client with the driver adapter: diff --git a/apps/docs/content/docs/guides/making-guides.mdx b/apps/docs/content/docs/guides/making-guides.mdx index 21d0690c5e..9242c5244e 100644 --- a/apps/docs/content/docs/guides/making-guides.mdx +++ b/apps/docs/content/docs/guides/making-guides.mdx @@ -1,9 +1,9 @@ --- title: Writing guides -description: 'Learn how to write clear, consistent, and helpful guides for Prisma documentation' +description: "Learn how to write clear, consistent, and helpful guides for Prisma documentation" url: /guides/making-guides metaTitle: How to write guides for Prisma ORM -metaDescription: 'Learn how to write clear, consistent, and helpful guides for Prisma ORM documentation' +metaDescription: "Learn how to write clear, consistent, and helpful guides for Prisma ORM documentation" --- ## Introduction @@ -27,8 +27,8 @@ Every guide must include the following frontmatter at the top of the file: ```mdx --- -title: '[Descriptive title]' -description: '[One-sentence summary of what the guide covers]' +title: "[Descriptive title]" +description: "[One-sentence summary of what the guide covers]" --- ``` @@ -127,6 +127,7 @@ export default defineConfig({ - Code elements: `` `PrismaClient` `` - Package manager commands: Use ` ```npm ` blocks (see [Package manager commands](#package-manager-commands)) - Use admonitions for important information: + ```markdown :::info Context or background information @@ -144,21 +145,22 @@ export default defineConfig({ Helpful suggestions or best practices ::: ``` + - Use proper heading hierarchy (never skip levels) - Use numbered sections (e.g., "## 1. Setup", "### 1.1. Install") - Link to other documentation pages using relative paths (e.g., `[Database drivers](/orm/core-concepts/supported-databases/database-drivers)`) ## Guide categories -| Category | Directory | Description | Examples | -|----------|-----------|-------------|----------| -| **Framework** | `guides/frameworks/` | Integrate Prisma with frameworks | [Next.js](/guides/frameworks/nextjs), [NestJS](/guides/frameworks/nestjs), [SvelteKit](/guides/frameworks/sveltekit) | -| **Deployment** | `guides/deployment/` | Deploy apps and set up monorepos | [Turborepo](/guides/deployment/turborepo), [Cloudflare Workers](/guides/deployment/cloudflare-workers) | -| **Integration** | `guides/integrations/` | Use Prisma with platforms and tools | [GitHub Actions](/guides/integrations/github-actions), [Supabase](/guides/integrations/supabase-accelerate) | -| **Database** | `guides/database/` | Database patterns and migrations | [Multiple databases](/guides/database/multiple-databases), [Data migration](/guides/database/data-migration) | -| **Authentication** | `guides/authentication/` | Authentication patterns with Prisma | [Auth.js + Next.js](/guides/authentication/authjs/nextjs), [Better Auth + Next.js](/guides/authentication/better-auth/nextjs), [Clerk + Next.js](/guides/authentication/clerk/nextjs) | -| **Prisma Postgres** | `guides/postgres/` | Prisma Postgres features | [Vercel](/guides/postgres/vercel), [Netlify](/guides/postgres/netlify), [Viewing data](/guides/postgres/viewing-data) | -| **Migration** | `guides/switch-to-prisma-orm/` | Switch from other ORMs | [From Mongoose](/guides/switch-to-prisma-orm/from-mongoose), [From Drizzle](/guides/switch-to-prisma-orm/from-drizzle) | +| Category | Directory | Description | Examples | +| ------------------- | ------------------------------ | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Framework** | `guides/frameworks/` | Integrate Prisma with frameworks | [Next.js](/guides/frameworks/nextjs), [NestJS](/guides/frameworks/nestjs), [SvelteKit](/guides/frameworks/sveltekit) | +| **Deployment** | `guides/deployment/` | Deploy apps and set up monorepos | [Turborepo](/guides/deployment/turborepo), [Cloudflare Workers](/guides/deployment/cloudflare-workers) | +| **Integration** | `guides/integrations/` | Use Prisma with platforms and tools | [GitHub Actions](/guides/integrations/github-actions), [Supabase](/guides/integrations/supabase-accelerate) | +| **Database** | `guides/database/` | Database patterns and migrations | [Multiple databases](/guides/database/multiple-databases), [Data migration](/guides/database/data-migration) | +| **Authentication** | `guides/authentication/` | Authentication patterns with Prisma | [Auth.js + Next.js](/guides/authentication/authjs/nextjs), [Better Auth + Next.js](/guides/authentication/better-auth/nextjs), [Clerk + Next.js](/guides/authentication/clerk/nextjs) | +| **Prisma Postgres** | `guides/postgres/` | Prisma Postgres features | [Vercel](/guides/postgres/vercel), [Netlify](/guides/postgres/netlify), [Viewing data](/guides/postgres/viewing-data) | +| **Migration** | `guides/switch-to-prisma-orm/` | Switch from other ORMs | [From Mongoose](/guides/switch-to-prisma-orm/from-mongoose), [From Drizzle](/guides/switch-to-prisma-orm/from-drizzle) | ## Common patterns diff --git a/apps/docs/content/docs/guides/runtimes/meta.json b/apps/docs/content/docs/guides/runtimes/meta.json index a28a3b2a2e..e44177cc17 100644 --- a/apps/docs/content/docs/guides/runtimes/meta.json +++ b/apps/docs/content/docs/guides/runtimes/meta.json @@ -1 +1 @@ -{"title":"Runtimes","pages":["bun","deno"]} +{ "title": "Runtimes", "pages": ["bun", "deno"] } diff --git a/apps/docs/content/docs/guides/switch-to-prisma-orm/from-sql-orms.mdx b/apps/docs/content/docs/guides/switch-to-prisma-orm/from-sql-orms.mdx index a5efb61768..f1a1b9200e 100644 --- a/apps/docs/content/docs/guides/switch-to-prisma-orm/from-sql-orms.mdx +++ b/apps/docs/content/docs/guides/switch-to-prisma-orm/from-sql-orms.mdx @@ -11,6 +11,7 @@ metaDescription: Learn how to migrate from TypeORM to Prisma ORM This guide shows you how to migrate your application from Sequelize or TypeORM to Prisma ORM. You can learn how Prisma ORM compares to these ORMs on the comparison pages: + - [Prisma ORM vs Sequelize](/orm/more/comparisons/prisma-and-sequelize) - [Prisma ORM vs TypeORM](/orm/more/comparisons/prisma-and-typeorm) @@ -91,20 +92,17 @@ const user = await User.findOne({ where: { id: 1 } }); const users = await User.findAll({ where: { active: true }, limit: 10, - order: [['createdAt', 'DESC']] + order: [["createdAt", "DESC"]], }); // Create const user = await User.create({ - name: 'Alice', - email: 'alice@example.com' + name: "Alice", + email: "alice@example.com", }); // Update -await User.update( - { name: 'Alicia' }, - { where: { id: 1 } } -); +await User.update({ name: "Alicia" }, { where: { id: 1 } }); // Delete await User.destroy({ where: { id: 1 } }); @@ -112,13 +110,13 @@ await User.destroy({ where: { id: 1 } }); // With relations const posts = await Post.findAll({ include: [{ model: User }], - limit: 10 + limit: 10, }); // Transaction const result = await sequelize.transaction(async (t) => { - const user = await User.create({ name: 'Alice' }, { transaction: t }); - const post = await Post.create({ title: 'Hello', userId: user.id }, { transaction: t }); + const user = await User.create({ name: "Alice" }, { transaction: t }); + const post = await Post.create({ title: "Hello", userId: user.id }, { transaction: t }); return { user, post }; }); ``` @@ -129,33 +127,33 @@ const user = await userRepository.findOne({ where: { id: 1 } }); const users = await userRepository.find({ where: { active: true }, take: 10, - order: { createdAt: 'DESC' } + order: { createdAt: "DESC" }, }); // Create const user = userRepository.create({ - name: 'Alice', - email: 'alice@example.com' + name: "Alice", + email: "alice@example.com", }); await userRepository.save(user); // Update -await userRepository.update(1, { name: 'Alicia' }); +await userRepository.update(1, { name: "Alicia" }); // Delete await userRepository.delete(1); // With relations const posts = await postRepository.find({ - relations: ['author'], - take: 10 + relations: ["author"], + take: 10, }); // Transaction await connection.transaction(async (manager) => { - const user = manager.create(User, { name: 'Alice' }); + const user = manager.create(User, { name: "Alice" }); await manager.save(user); - const post = manager.create(Post, { title: 'Hello', author: user }); + const post = manager.create(Post, { title: "Hello", author: user }); await manager.save(post); }); ``` @@ -166,21 +164,21 @@ const user = await prisma.user.findUnique({ where: { id: 1 } }); const users = await prisma.user.findMany({ where: { active: true }, take: 10, - orderBy: { createdAt: 'desc' } + orderBy: { createdAt: "desc" }, }); // Create const user = await prisma.user.create({ data: { - name: 'Alice', - email: 'alice@example.com' - } + name: "Alice", + email: "alice@example.com", + }, }); // Update await prisma.user.update({ where: { id: 1 }, - data: { name: 'Alicia' } + data: { name: "Alicia" }, }); // Delete @@ -189,17 +187,17 @@ await prisma.user.delete({ where: { id: 1 } }); // With relations const posts = await prisma.post.findMany({ include: { author: true }, - take: 10 + take: 10, }); // Transaction const result = await prisma.$transaction(async (tx) => { const user = await tx.user.create({ data: { - name: 'Alice', - posts: { create: { title: 'Hello' } } + name: "Alice", + posts: { create: { title: "Hello" } }, }, - include: { posts: true } + include: { posts: true }, }); return user; }); diff --git a/apps/docs/content/docs/guides/upgrade-prisma-orm/meta.json b/apps/docs/content/docs/guides/upgrade-prisma-orm/meta.json index e9978e2ec5..6695137bda 100644 --- a/apps/docs/content/docs/guides/upgrade-prisma-orm/meta.json +++ b/apps/docs/content/docs/guides/upgrade-prisma-orm/meta.json @@ -1,11 +1,4 @@ { "title": "Upgrade Prisma ORM", - "pages": [ - "v7", - "v6", - "v5", - "v4", - "v3", - "v1" - ] + "pages": ["v7", "v6", "v5", "v4", "v3", "v1"] } diff --git a/apps/docs/content/docs/guides/upgrade-prisma-orm/v1.mdx b/apps/docs/content/docs/guides/upgrade-prisma-orm/v1.mdx index 6bdf8d2511..94df80ee17 100644 --- a/apps/docs/content/docs/guides/upgrade-prisma-orm/v1.mdx +++ b/apps/docs/content/docs/guides/upgrade-prisma-orm/v1.mdx @@ -2,9 +2,8 @@ title: Upgrade to v1 description: Comprehensive guide for upgrading from Prisma 1 to Prisma ORM v1 url: /guides/upgrade-prisma-orm/v1 -metaTitle: 'Upgrade from Prisma 1 to Prisma ORM 2' -metaDescription: 'Upgrading your project from Prisma 1 to Prisma ORM 2' - +metaTitle: "Upgrade from Prisma 1 to Prisma ORM 2" +metaDescription: "Upgrading your project from Prisma 1 to Prisma ORM 2" --- This guide provides a comprehensive roadmap for migrating your project from Prisma 1 to the latest version of Prisma ORM. The migration process involves significant architectural changes and requires careful planning and execution. @@ -20,13 +19,13 @@ This guide provides a comprehensive roadmap for migrating your project from Pris ### Architectural changes -| Feature | Prisma 1 | Prisma ORM | -|---------|----------|------------| -| **Database Connection** | Uses Prisma Server as a proxy | Direct database connection | -| **API** | GraphQL API for database | Programmatic access via Prisma Client | -| **Schema** | GraphQL SDL + `prisma.yml` | Unified Prisma schema | -| **Modeling** | GraphQL SDL | Prisma Schema Language (PSL) | -| **Workflow** | `prisma deploy` | `prisma migrate` and `prisma db` commands | +| Feature | Prisma 1 | Prisma ORM | +| ----------------------- | ----------------------------- | ----------------------------------------- | +| **Database Connection** | Uses Prisma Server as a proxy | Direct database connection | +| **API** | GraphQL API for database | Programmatic access via Prisma Client | +| **Schema** | GraphQL SDL + `prisma.yml` | Unified Prisma schema | +| **Modeling** | GraphQL SDL | Prisma Schema Language (PSL) | +| **Workflow** | `prisma deploy` | `prisma migrate` and `prisma db` commands | ### Feature changes @@ -108,12 +107,12 @@ model Post { #### 3.1 Handling Special Types -| Prisma 1 Type | Prisma ORM Equivalent | Notes | -|---------------|----------------------|-------| -| `ID` | `String @id @default(cuid())` | Add `@id` directive | -| `DateTime` | `DateTime` | No change needed | -| `Json` | `Json` | No change needed | -| `Enum` | `Enum` | Define enums in the schema | +| Prisma 1 Type | Prisma ORM Equivalent | Notes | +| ------------- | ----------------------------- | -------------------------- | +| `ID` | `String @id @default(cuid())` | Add `@id` directive | +| `DateTime` | `DateTime` | No change needed | +| `Json` | `Json` | No change needed | +| `Enum` | `Enum` | Define enums in the schema | #### 3.2 Relation Handling @@ -139,19 +138,19 @@ model Post { ```typescript // Before (Prisma 1) -import { prisma } from './generated/prisma-client'; +import { prisma } from "./generated/prisma-client"; async function getUser(id: string) { return prisma.user({ id }); } // After (Prisma ORM) -import { PrismaClient } from '@prisma/client'; +import { PrismaClient } from "@prisma/client"; const prisma = new PrismaClient(); async function getUser(id: number) { return prisma.user.findUnique({ - where: { id } + where: { id }, }); } ``` @@ -168,7 +167,7 @@ const posts = await prisma.user({ id: 1 }).posts(); // After (Prisma ORM) const user = await prisma.user.findUnique({ where: { id: 1 }, - include: { posts: true } + include: { posts: true }, }); const posts = user?.posts; ``` @@ -178,16 +177,16 @@ const posts = user?.posts; ```typescript // Before (Prisma 1) const newUser = await prisma.createUser({ - name: 'Alice', - email: 'alice@example.com' + name: "Alice", + email: "alice@example.com", }); // After (Prisma ORM) const newUser = await prisma.user.create({ data: { - name: 'Alice', - email: 'alice@example.com' - } + name: "Alice", + email: "alice@example.com", + }, }); ``` @@ -200,23 +199,23 @@ Test all CRUD operations to ensure data consistency: ```typescript // Test create const user = await prisma.user.create({ - data: { name: 'Test', email: 'test@example.com' } + data: { name: "Test", email: "test@example.com" }, }); // Test read const foundUser = await prisma.user.findUnique({ - where: { id: user.id } + where: { id: user.id }, }); // Test update const updatedUser = await prisma.user.update({ where: { id: user.id }, - data: { name: 'Updated Name' } + data: { name: "Updated Name" }, }); // Test delete await prisma.user.delete({ - where: { id: user.id } + where: { id: user.id }, }); ``` @@ -230,25 +229,25 @@ const userWithPosts = await prisma.user.findUnique({ where: { id: 1 }, include: { posts: true, - profile: true - } + profile: true, + }, }); // Test nested writes const userWithNewPost = await prisma.user.create({ data: { - name: 'Bob', - email: 'bob@example.com', + name: "Bob", + email: "bob@example.com", posts: { create: { - title: 'Hello World', - content: 'This is my first post' - } - } + title: "Hello World", + content: "This is my first post", + }, + }, }, include: { - posts: true - } + posts: true, + }, }); ``` @@ -281,14 +280,14 @@ FOR EACH ROW EXECUTE FUNCTION notify_new_post(); // Publish event when creating a post const post = await prisma.post.create({ data: { - title: 'New Post', - content: 'Content', - author: { connect: { id: userId }} - } + title: "New Post", + content: "Content", + author: { connect: { id: userId } }, + }, }); // Publish event to your pub/sub system -await pubsub.publish('POST_CREATED', { postCreated: post }); +await pubsub.publish("POST_CREATED", { postCreated: post }); ``` ### 2. Authentication @@ -296,16 +295,16 @@ await pubsub.publish('POST_CREATED', { postCreated: post }); If you were using Prisma 1's built-in authentication, you'll need to implement your own solution: ```typescript -import { compare } from 'bcryptjs'; -import { sign } from 'jsonwebtoken'; +import { compare } from "bcryptjs"; +import { sign } from "jsonwebtoken"; export async function login(email: string, password: string) { const user = await prisma.user.findUnique({ where: { email } }); - if (!user) throw new Error('User not found'); - + if (!user) throw new Error("User not found"); + const valid = await compare(password, user.password); - if (!valid) throw new Error('Invalid password'); - + if (!valid) throw new Error("Invalid password"); + const token = sign({ userId: user.id }, process.env.APP_SECRET!); return { token, user }; } @@ -326,6 +325,7 @@ prisma1-upgrade ``` This tool helps with: + - Converting your Prisma 1 datamodel to Prisma schema - Identifying potential issues in your schema - Providing migration recommendations @@ -333,14 +333,15 @@ This tool helps with: ## Performance Considerations 1. **Connection Pooling**: Configure connection pooling for better performance: + ```typescript const prisma = new PrismaClient({ - log: ['query', 'info', 'warn', 'error'], + log: ["query", "info", "warn", "error"], datasources: { db: { - url: process.env.DATABASE_URL + '&connection_limit=20' - } - } + url: process.env.DATABASE_URL + "&connection_limit=20", + }, + }, }); ``` @@ -351,8 +352,8 @@ This tool helps with: select: { id: true, name: true, - email: true - } + email: true, + }, }); ``` @@ -367,6 +368,7 @@ This tool helps with: ## Getting Help If you encounter issues during migration: + 1. Search the [GitHub Issues](https://github.com/prisma/prisma/issues) 2. Ask for help in the [Prisma Slack](https://slack.prisma.io/) 3. Open a [GitHub Discussion](https://github.com/prisma/prisma/discussions) diff --git a/apps/docs/content/docs/guides/upgrade-prisma-orm/v3.mdx b/apps/docs/content/docs/guides/upgrade-prisma-orm/v3.mdx index 37e11d9b9c..7cfc39cdc9 100644 --- a/apps/docs/content/docs/guides/upgrade-prisma-orm/v3.mdx +++ b/apps/docs/content/docs/guides/upgrade-prisma-orm/v3.mdx @@ -2,9 +2,8 @@ title: Upgrade to v3 description: Comprehensive guide for upgrading to Prisma ORM v3 url: /guides/upgrade-prisma-orm/v3 -metaTitle: 'Upgrade to Prisma ORM 3' -metaDescription: 'Guides on how to upgrade to Prisma ORM 3' - +metaTitle: "Upgrade to Prisma ORM 3" +metaDescription: "Guides on how to upgrade to Prisma ORM 3" --- This guide helps you upgrade your project to Prisma ORM v3. Version 3 includes several important changes that may affect your application, so please review this guide carefully before proceeding. @@ -51,8 +50,8 @@ model User { model Post { id Int @id @default(autoincrement()) author User @relation( - fields: [authorId], - references: [id], + fields: [authorId], + references: [id], onDelete: Cascade, // Explicit action required onUpdate: Cascade // Optional: define update behavior ) @@ -109,7 +108,7 @@ The `$queryRaw` method now only supports template literals for security: ```typescript // String syntax (no longer supported in v3) -const result = await prisma.$queryRaw('SELECT * FROM User WHERE id = 1'); +const result = await prisma.$queryRaw("SELECT * FROM User WHERE id = 1"); ``` #### After (Prisma 3.x): @@ -120,10 +119,7 @@ const userId = 1; const result = await prisma.$queryRaw`SELECT * FROM User WHERE id = ${userId}`; // Or use $queryRawUnsafe (use with caution) -const unsafeResult = await prisma.$queryRawUnsafe( - 'SELECT * FROM User WHERE id = $1', - userId -); +const unsafeResult = await prisma.$queryRawUnsafe("SELECT * FROM User WHERE id = $1", userId); ``` **Action Required**: Update all `$queryRaw` calls in your codebase to use template literals or switch to `$queryRawUnsafe` with proper parameterization. @@ -145,49 +141,49 @@ Prisma ORM 3 introduces more precise handling of null values in JSON fields: const result = await prisma.log.findMany({ where: { meta: { - equals: null // Could mean either JSON null or database NULL - } - } + equals: null, // Could mean either JSON null or database NULL + }, + }, }); ``` #### After (Prisma 3.x): ```typescript -import { Prisma } from '@prisma/client'; +import { Prisma } from "@prisma/client"; // Check for JSON null const jsonNullResults = await prisma.log.findMany({ where: { meta: { - equals: Prisma.JsonNull - } - } + equals: Prisma.JsonNull, + }, + }, }); // Check for database NULL const dbNullResults = await prisma.log.findMany({ where: { meta: { - equals: Prisma.DbNull - } - } + equals: Prisma.DbNull, + }, + }, }); // Check for either const anyNullResults = await prisma.log.findMany({ where: { meta: { - equals: Prisma.AnyNull - } - } + equals: Prisma.AnyNull, + }, + }, }); // Creating with specific null types await prisma.log.create({ data: { - meta: Prisma.JsonNull // or Prisma.DbNull - } + meta: Prisma.JsonNull, // or Prisma.DbNull + }, }); ``` diff --git a/apps/docs/content/docs/guides/upgrade-prisma-orm/v4.mdx b/apps/docs/content/docs/guides/upgrade-prisma-orm/v4.mdx index 2035e9ad0e..4b8a5f13dd 100644 --- a/apps/docs/content/docs/guides/upgrade-prisma-orm/v4.mdx +++ b/apps/docs/content/docs/guides/upgrade-prisma-orm/v4.mdx @@ -2,9 +2,8 @@ title: Upgrade to v4 description: Comprehensive guide for upgrading to Prisma ORM v4 url: /guides/upgrade-prisma-orm/v4 -metaTitle: 'Upgrade to Prisma ORM 4' -metaDescription: 'Guides on how to upgrade to Prisma ORM 4' - +metaTitle: "Upgrade to Prisma ORM 4" +metaDescription: "Guides on how to upgrade to Prisma ORM 4" --- This guide helps you upgrade your project to Prisma ORM 4. Version 4 includes several important changes that may affect your application, so please review this guide carefully before proceeding. @@ -48,7 +47,6 @@ npm install -D prisma@4 npx prisma generate ``` - :::danger Before you upgrade, check each breaking change below to see how the upgrade might affect your application. @@ -141,12 +139,12 @@ This will update your schema with new capabilities like improved index configura In Prisma 4, the return types of `queryRaw` and `queryRawUnsafe` have changed: -| Data Type | Before 4.0.0 | From 4.0.0 | -|------------|---------------|---------------| -| `DateTime` | `String` | `Date` | -| `BigInt` | `String` | `BigInt` | -| `Bytes` | `String` | `Buffer` | -| `Decimal` | `String` | `Decimal` | +| Data Type | Before 4.0.0 | From 4.0.0 | +| ---------- | ------------ | ---------- | +| `DateTime` | `String` | `Date` | +| `BigInt` | `String` | `BigInt` | +| `Bytes` | `String` | `Buffer` | +| `Decimal` | `String` | `Decimal` | **Update your code** to handle these type changes, especially if you're using TypeScript or performing type checks. @@ -158,17 +156,17 @@ Prisma 4 changes how JSON null values are handled. If you're working with JSON f // Before const result = await prisma.model.findMany({ where: { - jsonField: { equals: null } - } + jsonField: { equals: null }, + }, }); // After -import { Prisma } from '@prisma/client'; +import { Prisma } from "@prisma/client"; const result = await prisma.model.findMany({ where: { - jsonField: { equals: Prisma.JsonNull } - } + jsonField: { equals: Prisma.JsonNull }, + }, }); ``` @@ -233,6 +231,7 @@ model User { ### 3. Better Type Safety Prisma 4 improves type safety with: + - Stricter validation of relation fields - Better error messages for common mistakes - Improved type inference for complex queries @@ -288,6 +287,7 @@ Prisma 4 improves type safety with: ## Getting Help If you encounter issues during the upgrade: + 1. Check the [GitHub Issues](https://github.com/prisma/prisma/issues) for known problems 2. Ask for help in the [Prisma Slack](https://slack.prisma.io/) 3. Open a [GitHub Discussion](https://github.com/prisma/prisma/discussions) diff --git a/apps/docs/content/docs/guides/upgrade-prisma-orm/v5.mdx b/apps/docs/content/docs/guides/upgrade-prisma-orm/v5.mdx index 7a9964e058..c1bdb580cd 100644 --- a/apps/docs/content/docs/guides/upgrade-prisma-orm/v5.mdx +++ b/apps/docs/content/docs/guides/upgrade-prisma-orm/v5.mdx @@ -2,9 +2,8 @@ title: Upgrade to v5 description: Comprehensive guide for upgrading to Prisma ORM v5 url: /guides/upgrade-prisma-orm/v5 -metaTitle: 'Upgrade to Prisma ORM 5' -metaDescription: 'Guides on how to upgrade to Prisma ORM 5' - +metaTitle: "Upgrade to Prisma ORM 5" +metaDescription: "Guides on how to upgrade to Prisma ORM 5" --- Prisma ORM v5.0.0 introduces a number of changes, including the usage of our new JSON Protocol, [which makes Prisma Client faster by default](https://www.prisma.io/blog/prisma-5-f66prwkjx72s). A full list of these changes can be found [in our release notes](https://github.com/prisma/prisma/releases/tag/5.0.0). @@ -15,7 +14,6 @@ This guide explains how upgrading might affect your application and gives instru To upgrade to Prisma ORM 5 from an earlier version, you need to update both the `prisma` and `@prisma/client` packages. - ```npm npm install @prisma/client@5 npm install -D prisma@5 diff --git a/apps/docs/content/docs/guides/upgrade-prisma-orm/v6.mdx b/apps/docs/content/docs/guides/upgrade-prisma-orm/v6.mdx index 01c436ac52..fa29d7ca0a 100644 --- a/apps/docs/content/docs/guides/upgrade-prisma-orm/v6.mdx +++ b/apps/docs/content/docs/guides/upgrade-prisma-orm/v6.mdx @@ -2,9 +2,8 @@ title: Upgrade to v6 description: Comprehensive guide for upgrading to Prisma ORM v6 url: /guides/upgrade-prisma-orm/v6 -metaTitle: 'Upgrade to Prisma ORM 6' -metaDescription: 'Guides on how to upgrade to Prisma ORM 6' - +metaTitle: "Upgrade to Prisma ORM 6" +metaDescription: "Guides on how to upgrade to Prisma ORM 6" --- Prisma ORM v6 introduces a number of **breaking changes** when you upgrade from an earlier Prisma ORM version. This guide explains how this upgrade might affect your application and gives instructions on how to handle any changes. @@ -98,7 +97,7 @@ CREATE TABLE "_PostToTag" ( ); -- CreateIndex -CREATE UNIQUE INDEX "_PostToTag_AB_unique" ON "_PostToTag"("A", "B"); +CREATE UNIQUE INDEX "_PostToTag_AB_unique" ON "_PostToTag"("A", "B"); -- CreateIndex CREATE INDEX "_PostToTag_B_index" ON "_PostToTag"("B"); @@ -118,7 +117,7 @@ CREATE TABLE "_PostToTag" ( "A" INTEGER NOT NULL, "B" INTEGER NOT NULL, - CONSTRAINT "_PostToTag_AB_pkey" PRIMARY KEY ("A","B") + CONSTRAINT "_PostToTag_AB_pkey" PRIMARY KEY ("A","B") ); -- CreateIndex @@ -165,7 +164,7 @@ datasource db { generator client { provider = "prisma-client-js" - previewFeatures = ["fullTextSearch"] + previewFeatures = ["fullTextSearch"] } ``` @@ -179,7 +178,7 @@ datasource db { generator client { provider = "prisma-client-js" - previewFeatures = ["fullTextSearchPostgres"] + previewFeatures = ["fullTextSearchPostgres"] } ``` diff --git a/apps/docs/content/docs/guides/upgrade-prisma-orm/v7.mdx b/apps/docs/content/docs/guides/upgrade-prisma-orm/v7.mdx index fab5145120..8322064f3a 100644 --- a/apps/docs/content/docs/guides/upgrade-prisma-orm/v7.mdx +++ b/apps/docs/content/docs/guides/upgrade-prisma-orm/v7.mdx @@ -2,9 +2,8 @@ title: Upgrade to v7 description: Comprehensive guide for upgrading to Prisma ORM v7 url: /guides/upgrade-prisma-orm/v7 -metaTitle: 'Upgrade to Prisma ORM 7' -metaDescription: 'Guide on how to upgrade to Prisma ORM 7' - +metaTitle: "Upgrade to Prisma ORM 7" +metaDescription: "Guide on how to upgrade to Prisma ORM 7" --- Prisma ORM v7 introduces **breaking changes** when you upgrade from an earlier Prisma ORM version. This guide explains how this upgrade might affect your application and gives instructions on how to handle any changes. @@ -31,7 +30,6 @@ If you are using MongoDB, please note that Prisma ORM v7 does not yet support Mo To upgrade to Prisma ORM v7 from an earlier version, you need to update both the `prisma` and `@prisma/client` packages: - ```npm npm install @prisma/client@7 npm install -D prisma@7 @@ -86,7 +84,6 @@ queries, smaller bundle size, and require less system resources when deployed to Additionally, the `output` field is now **required** in the generator block. Prisma Client will no longer be generated in `node_modules` by default. You must specify a custom output path. - ```prisma tab="Before" generator client { provider = "prisma-client-js" @@ -94,7 +91,6 @@ generator client { } ``` - ```prisma tab="After" generator client { provider = "prisma-client" diff --git a/apps/docs/content/docs/management-api/api-clients.mdx b/apps/docs/content/docs/management-api/api-clients.mdx index 923bb7df21..96e8496238 100644 --- a/apps/docs/content/docs/management-api/api-clients.mdx +++ b/apps/docs/content/docs/management-api/api-clients.mdx @@ -1,6 +1,6 @@ --- title: Using API Clients -description: 'Use the Management API with popular API clients like Postman, Insomnia, and Yaak' +description: "Use the Management API with popular API clients like Postman, Insomnia, and Yaak" url: /management-api/api-clients metaTitle: How to use the Management API with API Clients metaDescription: Learn how to use the Management API with API Clients @@ -196,15 +196,15 @@ Now you'll set up authentication in Yaak: 5. Set the authentication type to **OAuth 2.0** 6. Enter the following values: -| Parameter | Value | -| ------------------ | -------------------------------------- | -| Grant Type | Authorization Code | -| Authorization URL | `https://auth.prisma.io/authorize` | -| Token URL | `https://auth.prisma.io/token` | -| Client ID | `your-client-id` | -| Client Secret | `your-client-secret` | -| Redirect URL | `https://devnull.yaak.app/callback` | -| Scope | `workspace:admin` | +| Parameter | Value | +| ----------------- | ----------------------------------- | +| Grant Type | Authorization Code | +| Authorization URL | `https://auth.prisma.io/authorize` | +| Token URL | `https://auth.prisma.io/token` | +| Client ID | `your-client-id` | +| Client Secret | `your-client-secret` | +| Redirect URL | `https://devnull.yaak.app/callback` | +| Scope | `workspace:admin` | 7. Click **Get Token** 8. A browser window will open and have you complete the authorization flow diff --git a/apps/docs/content/docs/management-api/authentication.mdx b/apps/docs/content/docs/management-api/authentication.mdx index 17e15cce48..53c6daba1a 100644 --- a/apps/docs/content/docs/management-api/authentication.mdx +++ b/apps/docs/content/docs/management-api/authentication.mdx @@ -2,7 +2,7 @@ title: Authentication description: Learn how to authenticate with the Prisma Management API using service tokens or OAuth 2.0 metaTitle: Management API Authentication | Service Tokens & OAuth 2.0 -metaDescription: 'Authenticate with Prisma Management API: service tokens for scripts and CI/CD, or OAuth 2.0 with PKCE for user-facing apps. Bearer token in Authorization header.' +metaDescription: "Authenticate with Prisma Management API: service tokens for scripts and CI/CD, or OAuth 2.0 with PKCE for user-facing apps. Bearer token in Authorization header." url: /management-api/authentication --- @@ -76,6 +76,7 @@ This provides enhanced security, especially for mobile and single-page applicati :::note[Development redirect URIs] For local development, the following redirect URIs are accepted with any port via wildcard matching: + - `localhost` (e.g., `http://localhost:3000/callback`) - `127.0.0.1` (e.g., `http://127.0.0.1:3000/callback`) - `[::1]` - IPv6 loopback (e.g., `http://[::1]:3000/callback`) @@ -84,11 +85,11 @@ For local development, the following redirect URIs are accepted with any port vi ### OAuth Endpoints -| Endpoint | URL | -|----------|-----| -| Authorization | `https://auth.prisma.io/authorize` | -| Token | `https://auth.prisma.io/token` | -| Discovery | `https://auth.prisma.io/.well-known/oauth-authorization-server` | +| Endpoint | URL | +| ------------- | --------------------------------------------------------------- | +| Authorization | `https://auth.prisma.io/authorize` | +| Token | `https://auth.prisma.io/token` | +| Discovery | `https://auth.prisma.io/.well-known/oauth-authorization-server` | :::tip @@ -98,17 +99,17 @@ The discovery endpoint provides OAuth server metadata that can be used for autom ### Available Scopes -| Scope | Description | -|-------|-------------| -| `workspace:admin` | Full access to workspace resources | -| `offline_access` | Enables refresh tokens for long-lived sessions | +| Scope | Description | +| ----------------- | ---------------------------------------------- | +| `workspace:admin` | Full access to workspace resources | +| `offline_access` | Enables refresh tokens for long-lived sessions | ### Token Lifetimes -| Token Type | Expiration | -|-----------|------------| -| Access tokens | 1 hour | -| Refresh tokens | 90 days | +| Token Type | Expiration | +| -------------- | ---------- | +| Access tokens | 1 hour | +| Refresh tokens | 90 days | ### OAuth Authorization Flow @@ -116,12 +117,12 @@ The discovery endpoint provides OAuth server metadata that can be used for autom Redirect users to the authorization endpoint with the following query parameters: -| Parameter | Description | -|-----------|-------------| -| `client_id` | Your OAuth application's Client ID | -| `redirect_uri` | The callback URL where users will be redirected after authorization | -| `response_type` | Must be `code` for the authorization code flow | -| `scope` | Permissions to request (e.g., `workspace:admin`) | +| Parameter | Description | +| --------------- | ------------------------------------------------------------------- | +| `client_id` | Your OAuth application's Client ID | +| `redirect_uri` | The callback URL where users will be redirected after authorization | +| `response_type` | Must be `code` for the authorization code flow | +| `scope` | Permissions to request (e.g., `workspace:admin`) | ``` https://auth.prisma.io/authorize?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&response_type=code&scope=workspace:admin diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/delete-compute-services-by-compute-service-id.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/delete-compute-services-by-compute-service-id.mdx index 7ab940e39c..b1ca93c4a7 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/delete-compute-services-by-compute-service-id.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/delete-compute-services-by-compute-service-id.mdx @@ -1,6 +1,6 @@ --- title: Delete compute service -description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Deletes a compute service. All compute versions under the service must already be stopped or deleted.' +description: "Experimental endpoint: this API is in active development and may change at any time without notice. Deletes a compute service. All compute versions under the service must already be stopped or deleted." full: true _openapi: method: DELETE @@ -12,10 +12,10 @@ _openapi: ⚠️ Experimental endpoint: this API is in active development and may change at any time without notice. ⚠️ Deletes a compute service. All compute versions under the service must already be stopped or deleted. - path: '/v1/compute-services/{computeServiceId}' -url: '/management-api/endpoints/[experimental]/delete-compute-services-by-compute-service-id' -metaTitle: 'DELETE /v1/compute-services/{computeServiceId} | Delete compute service' -metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Deletes a compute service. All compute versions under the service must already be stopped or deleted.' + path: "/v1/compute-services/{computeServiceId}" +url: "/management-api/endpoints/[experimental]/delete-compute-services-by-compute-service-id" +metaTitle: "DELETE /v1/compute-services/{computeServiceId} | Delete compute service" +metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Deletes a compute service. All compute versions under the service must already be stopped or deleted." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,4 +24,7 @@ metaDescription: 'Management API: Experimental endpoint: this API is in active d Deletes a compute service. All compute versions under the service must already be stopped or deleted. - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/delete-compute-services-versions-by-version-id.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/delete-compute-services-versions-by-version-id.mdx index 5b09919281..917dd5cf6e 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/delete-compute-services-versions-by-version-id.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/delete-compute-services-versions-by-version-id.mdx @@ -1,6 +1,6 @@ --- title: Delete compute version -description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Permanently deletes the compute version, its metadata, and any associated VM. The version must be stopped or in the `new` state before it can be deleted.' +description: "Experimental endpoint: this API is in active development and may change at any time without notice. Permanently deletes the compute version, its metadata, and any associated VM. The version must be stopped or in the `new` state before it can be deleted." full: true _openapi: method: DELETE @@ -12,10 +12,10 @@ _openapi: ⚠️ Experimental endpoint: this API is in active development and may change at any time without notice. ⚠️ Permanently deletes the compute version, its metadata, and any associated VM. The version must be stopped or in the `new` state before it can be deleted. - path: '/v1/compute-services/versions/{versionId}' -url: '/management-api/endpoints/[experimental]/delete-compute-services-versions-by-version-id' -metaTitle: 'DELETE /v1/compute-services/versions/{versionId} | Delete compute version' -metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Permanently deletes the compute version, its metadata, and any associated VM. The version must be stopped or in the `new` state before it can be deleted.' + path: "/v1/compute-services/versions/{versionId}" +url: "/management-api/endpoints/[experimental]/delete-compute-services-versions-by-version-id" +metaTitle: "DELETE /v1/compute-services/versions/{versionId} | Delete compute version" +metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Permanently deletes the compute version, its metadata, and any associated VM. The version must be stopped or in the `new` state before it can be deleted." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,4 +24,7 @@ metaDescription: 'Management API: Experimental endpoint: this API is in active d Permanently deletes the compute version, its metadata, and any associated VM. The version must be stopped or in the `new` state before it can be deleted. - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/delete-versions-by-version-id.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/delete-versions-by-version-id.mdx index 331fe8dd45..0dae533b3e 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/delete-versions-by-version-id.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/delete-versions-by-version-id.mdx @@ -1,6 +1,6 @@ --- title: Delete compute version -description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Permanently deletes the compute version, its metadata, and any associated VM. The version must be stopped or in the `new` state before it can be deleted.' +description: "Experimental endpoint: this API is in active development and may change at any time without notice. Permanently deletes the compute version, its metadata, and any associated VM. The version must be stopped or in the `new` state before it can be deleted." full: true _openapi: method: DELETE @@ -12,10 +12,10 @@ _openapi: ⚠️ Experimental endpoint: this API is in active development and may change at any time without notice. ⚠️ Permanently deletes the compute version, its metadata, and any associated VM. The version must be stopped or in the `new` state before it can be deleted. - path: '/v1/versions/{versionId}' -url: '/management-api/endpoints/[experimental]/delete-versions-by-version-id' -metaTitle: 'DELETE /v1/versions/{versionId} | Delete compute version' -metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Permanently deletes the compute version, its metadata, and any associated VM. The version must be stopped or in the `new` state before it can be deleted.' + path: "/v1/versions/{versionId}" +url: "/management-api/endpoints/[experimental]/delete-versions-by-version-id" +metaTitle: "DELETE /v1/versions/{versionId} | Delete compute version" +metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Permanently deletes the compute version, its metadata, and any associated VM. The version must be stopped or in the `new` state before it can be deleted." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,4 +24,7 @@ metaDescription: 'Management API: Experimental endpoint: this API is in active d Permanently deletes the compute version, its metadata, and any associated VM. The version must be stopped or in the `new` state before it can be deleted. - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services-by-compute-service-id-versions.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services-by-compute-service-id-versions.mdx index a1bae85dc5..8203b67e95 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services-by-compute-service-id-versions.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services-by-compute-service-id-versions.mdx @@ -1,6 +1,6 @@ --- title: List compute versions -description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Returns all compute versions belonging to a compute service, ordered by creation time (newest first). Supports cursor-based pagination.' +description: "Experimental endpoint: this API is in active development and may change at any time without notice. Returns all compute versions belonging to a compute service, ordered by creation time (newest first). Supports cursor-based pagination." full: true _openapi: method: GET @@ -12,10 +12,10 @@ _openapi: ⚠️ Experimental endpoint: this API is in active development and may change at any time without notice. ⚠️ Returns all compute versions belonging to a compute service, ordered by creation time (newest first). Supports cursor-based pagination. - path: '/v1/compute-services/{computeServiceId}/versions' -url: '/management-api/endpoints/[experimental]/get-compute-services-by-compute-service-id-versions' -metaTitle: 'GET /v1/compute-services/{computeServiceId}/versions | List compute versions' -metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Returns all compute versions belonging to a compute service, ordered by creation time (newest first). Supports cursor-based pagination.' + path: "/v1/compute-services/{computeServiceId}/versions" +url: "/management-api/endpoints/[experimental]/get-compute-services-by-compute-service-id-versions" +metaTitle: "GET /v1/compute-services/{computeServiceId}/versions | List compute versions" +metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Returns all compute versions belonging to a compute service, ordered by creation time (newest first). Supports cursor-based pagination." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,4 +24,7 @@ metaDescription: 'Management API: Experimental endpoint: this API is in active d Returns all compute versions belonging to a compute service, ordered by creation time (newest first). Supports cursor-based pagination. - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services-by-compute-service-id.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services-by-compute-service-id.mdx index a1ceee61c2..026909b6d6 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services-by-compute-service-id.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services-by-compute-service-id.mdx @@ -1,6 +1,6 @@ --- title: Get compute service -description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Returns a compute service by ID, including its region and latest version reference.' +description: "Experimental endpoint: this API is in active development and may change at any time without notice. Returns a compute service by ID, including its region and latest version reference." full: true _openapi: method: GET @@ -12,10 +12,10 @@ _openapi: ⚠️ Experimental endpoint: this API is in active development and may change at any time without notice. ⚠️ Returns a compute service by ID, including its region and latest version reference. - path: '/v1/compute-services/{computeServiceId}' -url: '/management-api/endpoints/[experimental]/get-compute-services-by-compute-service-id' -metaTitle: 'GET /v1/compute-services/{computeServiceId} | Get compute service' -metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Returns a compute service by ID, including its region and latest version reference.' + path: "/v1/compute-services/{computeServiceId}" +url: "/management-api/endpoints/[experimental]/get-compute-services-by-compute-service-id" +metaTitle: "GET /v1/compute-services/{computeServiceId} | Get compute service" +metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Returns a compute service by ID, including its region and latest version reference." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,4 +24,7 @@ metaDescription: 'Management API: Experimental endpoint: this API is in active d Returns a compute service by ID, including its region and latest version reference. - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services-versions-by-version-id-logs.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services-versions-by-version-id-logs.mdx index a33a63e333..9164d44287 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services-versions-by-version-id-logs.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services-versions-by-version-id-logs.mdx @@ -12,9 +12,9 @@ _openapi: ⚠️ Experimental endpoint: this API is in active development and may change at any time without notice. ⚠️ Upgrades to a WebSocket connection that streams log output for the specified compute version. Each message is a JSON object with `type: "log"` (log text + byte metadata) or `type: "terminal"` (end-of-segment signal with reconnect cursor). The stream ends after 10 minutes; reconnect with the `cursor` query parameter to continue. - path: '/v1/compute-services/versions/{versionId}/logs' -url: '/management-api/endpoints/[experimental]/get-compute-services-versions-by-version-id-logs' -metaTitle: 'GET /v1/compute-services/versions/{versionId}/logs | Stream compute version logs via WebSocket' + path: "/v1/compute-services/versions/{versionId}/logs" +url: "/management-api/endpoints/[experimental]/get-compute-services-versions-by-version-id-logs" +metaTitle: "GET /v1/compute-services/versions/{versionId}/logs | Stream compute version logs via WebSocket" metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Upgrades to a WebSocket connection that streams log output for the specified compute version. Each message is a JSON object with `type: "log"` (log text + byte metadata) or `type: "terminal"` (end-of-segment signal with reconnect cursor). The stream ends after 10 minutes; reconnect with the `cursor` query parameter to continue.' --- @@ -24,4 +24,7 @@ metaDescription: 'Management API: Experimental endpoint: this API is in active d Upgrades to a WebSocket connection that streams log output for the specified compute version. Each message is a JSON object with `type: "log"` (log text + byte metadata) or `type: "terminal"` (end-of-segment signal with reconnect cursor). The stream ends after 10 minutes; reconnect with the `cursor` query parameter to continue. - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services-versions-by-version-id.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services-versions-by-version-id.mdx index 4b204bd0b7..a6750d4313 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services-versions-by-version-id.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services-versions-by-version-id.mdx @@ -1,6 +1,6 @@ --- title: Get compute version -description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Returns a compute version by ID, including its current status derived from the underlying VM state.' +description: "Experimental endpoint: this API is in active development and may change at any time without notice. Returns a compute version by ID, including its current status derived from the underlying VM state." full: true _openapi: method: GET @@ -12,10 +12,10 @@ _openapi: ⚠️ Experimental endpoint: this API is in active development and may change at any time without notice. ⚠️ Returns a compute version by ID, including its current status derived from the underlying VM state. - path: '/v1/compute-services/versions/{versionId}' -url: '/management-api/endpoints/[experimental]/get-compute-services-versions-by-version-id' -metaTitle: 'GET /v1/compute-services/versions/{versionId} | Get compute version' -metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Returns a compute version by ID, including its current status derived from the underlying VM state.' + path: "/v1/compute-services/versions/{versionId}" +url: "/management-api/endpoints/[experimental]/get-compute-services-versions-by-version-id" +metaTitle: "GET /v1/compute-services/versions/{versionId} | Get compute version" +metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Returns a compute version by ID, including its current status derived from the underlying VM state." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,4 +24,7 @@ metaDescription: 'Management API: Experimental endpoint: this API is in active d Returns a compute version by ID, including its current status derived from the underlying VM state. - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services.mdx index 57d2f3cc6a..e43dae0ed0 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services.mdx @@ -1,6 +1,6 @@ --- title: List compute services -description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Returns all compute services the token has access to, ordered by creation time (oldest first). Optionally filter by project ID. Supports cursor-based pagination.' +description: "Experimental endpoint: this API is in active development and may change at any time without notice. Returns all compute services the token has access to, ordered by creation time (oldest first). Optionally filter by project ID. Supports cursor-based pagination." full: true _openapi: method: GET @@ -13,9 +13,9 @@ _openapi: Returns all compute services the token has access to, ordered by creation time (oldest first). Optionally filter by project ID. Supports cursor-based pagination. path: /v1/compute-services -url: '/management-api/endpoints/[experimental]/get-compute-services' +url: "/management-api/endpoints/[experimental]/get-compute-services" metaTitle: GET /v1/compute-services | List compute services -metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Returns all compute services the token has access to, ordered by creation time (oldest first). Optionally filter by project ID. Supports cursor-based pagination.' +metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Returns all compute services the token has access to, ordered by creation time (oldest first). Optionally filter by project ID. Supports cursor-based pagination." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,4 +24,7 @@ metaDescription: 'Management API: Experimental endpoint: this API is in active d Returns all compute services the token has access to, ordered by creation time (oldest first). Optionally filter by project ID. Supports cursor-based pagination. - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/get-projects-by-project-id-compute-services.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/get-projects-by-project-id-compute-services.mdx index db07a93ba1..a6d77f6683 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/get-projects-by-project-id-compute-services.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/get-projects-by-project-id-compute-services.mdx @@ -1,6 +1,6 @@ --- title: List compute services for a project -description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Returns all compute services belonging to a project, ordered by creation time (oldest first). Supports cursor-based pagination.' +description: "Experimental endpoint: this API is in active development and may change at any time without notice. Returns all compute services belonging to a project, ordered by creation time (oldest first). Supports cursor-based pagination." full: true _openapi: method: GET @@ -12,10 +12,10 @@ _openapi: ⚠️ Experimental endpoint: this API is in active development and may change at any time without notice. ⚠️ Returns all compute services belonging to a project, ordered by creation time (oldest first). Supports cursor-based pagination. - path: '/v1/projects/{projectId}/compute-services' -url: '/management-api/endpoints/[experimental]/get-projects-by-project-id-compute-services' -metaTitle: 'GET /v1/projects/{projectId}/compute-services | List compute services for a project' -metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Returns all compute services belonging to a project, ordered by creation time (oldest first). Supports cursor-based pagination.' + path: "/v1/projects/{projectId}/compute-services" +url: "/management-api/endpoints/[experimental]/get-projects-by-project-id-compute-services" +metaTitle: "GET /v1/projects/{projectId}/compute-services | List compute services for a project" +metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Returns all compute services belonging to a project, ordered by creation time (oldest first). Supports cursor-based pagination." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,4 +24,7 @@ metaDescription: 'Management API: Experimental endpoint: this API is in active d Returns all compute services belonging to a project, ordered by creation time (oldest first). Supports cursor-based pagination. - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/get-versions-by-version-id.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/get-versions-by-version-id.mdx index 55e8a1815b..438afe9509 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/get-versions-by-version-id.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/get-versions-by-version-id.mdx @@ -1,6 +1,6 @@ --- title: Get compute version -description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Returns a compute version by ID, including its current status derived from the underlying VM state.' +description: "Experimental endpoint: this API is in active development and may change at any time without notice. Returns a compute version by ID, including its current status derived from the underlying VM state." full: true _openapi: method: GET @@ -12,10 +12,10 @@ _openapi: ⚠️ Experimental endpoint: this API is in active development and may change at any time without notice. ⚠️ Returns a compute version by ID, including its current status derived from the underlying VM state. - path: '/v1/versions/{versionId}' -url: '/management-api/endpoints/[experimental]/get-versions-by-version-id' -metaTitle: 'GET /v1/versions/{versionId} | Get compute version' -metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Returns a compute version by ID, including its current status derived from the underlying VM state.' + path: "/v1/versions/{versionId}" +url: "/management-api/endpoints/[experimental]/get-versions-by-version-id" +metaTitle: "GET /v1/versions/{versionId} | Get compute version" +metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Returns a compute version by ID, including its current status derived from the underlying VM state." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,4 +24,7 @@ metaDescription: 'Management API: Experimental endpoint: this API is in active d Returns a compute version by ID, including its current status derived from the underlying VM state. - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/get-versions.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/get-versions.mdx index 7fc3c5a8c1..66ebbc2e1e 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/get-versions.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/get-versions.mdx @@ -1,6 +1,6 @@ --- title: List compute versions -description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Returns all compute versions the token has access to, ordered by creation time (newest first). Optionally filter by compute service ID. Supports cursor-based pagination.' +description: "Experimental endpoint: this API is in active development and may change at any time without notice. Returns all compute versions the token has access to, ordered by creation time (newest first). Optionally filter by compute service ID. Supports cursor-based pagination." full: true _openapi: method: GET @@ -13,9 +13,9 @@ _openapi: Returns all compute versions the token has access to, ordered by creation time (newest first). Optionally filter by compute service ID. Supports cursor-based pagination. path: /v1/versions -url: '/management-api/endpoints/[experimental]/get-versions' +url: "/management-api/endpoints/[experimental]/get-versions" metaTitle: GET /v1/versions | List compute versions -metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Returns all compute versions the token has access to, ordered by creation time (newest first). Optionally filter by compute service ID. Supports cursor-based pagination.' +metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Returns all compute versions the token has access to, ordered by creation time (newest first). Optionally filter by compute service ID. Supports cursor-based pagination." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,4 +24,4 @@ metaDescription: 'Management API: Experimental endpoint: this API is in active d Returns all compute versions the token has access to, ordered by creation time (newest first). Optionally filter by compute service ID. Supports cursor-based pagination. - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/patch-compute-services-by-compute-service-id.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/patch-compute-services-by-compute-service-id.mdx index 4657c8882f..5365e334f2 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/patch-compute-services-by-compute-service-id.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/patch-compute-services-by-compute-service-id.mdx @@ -1,6 +1,6 @@ --- title: Update compute service -description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Updates the display name of a compute service.' +description: "Experimental endpoint: this API is in active development and may change at any time without notice. Updates the display name of a compute service." full: true _openapi: method: PATCH @@ -12,10 +12,10 @@ _openapi: ⚠️ Experimental endpoint: this API is in active development and may change at any time without notice. ⚠️ Updates the display name of a compute service. - path: '/v1/compute-services/{computeServiceId}' -url: '/management-api/endpoints/[experimental]/patch-compute-services-by-compute-service-id' -metaTitle: 'PATCH /v1/compute-services/{computeServiceId} | Update compute service' -metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Updates the display name of a compute service.' + path: "/v1/compute-services/{computeServiceId}" +url: "/management-api/endpoints/[experimental]/patch-compute-services-by-compute-service-id" +metaTitle: "PATCH /v1/compute-services/{computeServiceId} | Update compute service" +metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Updates the display name of a compute service." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,4 +24,7 @@ metaDescription: 'Management API: Experimental endpoint: this API is in active d Updates the display name of a compute service. - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services-by-compute-service-id-promote.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services-by-compute-service-id-promote.mdx index 94f3adadd3..eb98efd7a7 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services-by-compute-service-id-promote.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services-by-compute-service-id-promote.mdx @@ -1,6 +1,6 @@ --- title: Promote compute version -description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Promotes a compute version to be the active version behind the service''s stable endpoint. The version must be running. Returns the service endpoint domain.' +description: "Experimental endpoint: this API is in active development and may change at any time without notice. Promotes a compute version to be the active version behind the service's stable endpoint. The version must be running. Returns the service endpoint domain." full: true _openapi: method: POST @@ -12,10 +12,10 @@ _openapi: ⚠️ Experimental endpoint: this API is in active development and may change at any time without notice. ⚠️ Promotes a compute version to be the active version behind the service's stable endpoint. The version must be running. Returns the service endpoint domain. - path: '/v1/compute-services/{computeServiceId}/promote' -url: '/management-api/endpoints/[experimental]/post-compute-services-by-compute-service-id-promote' -metaTitle: 'POST /v1/compute-services/{computeServiceId}/promote | Promote compute version' -metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Promotes a compute version to be the active version behind the service''s stable endpoint. The version must be running. Returns the service endpoint domain.' + path: "/v1/compute-services/{computeServiceId}/promote" +url: "/management-api/endpoints/[experimental]/post-compute-services-by-compute-service-id-promote" +metaTitle: "POST /v1/compute-services/{computeServiceId}/promote | Promote compute version" +metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Promotes a compute version to be the active version behind the service's stable endpoint. The version must be running. Returns the service endpoint domain." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,4 +24,7 @@ metaDescription: 'Management API: Experimental endpoint: this API is in active d Promotes a compute version to be the active version behind the service's stable endpoint. The version must be running. Returns the service endpoint domain. - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services-by-compute-service-id-versions.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services-by-compute-service-id-versions.mdx index f05936c1f9..bf2875bbeb 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services-by-compute-service-id-versions.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services-by-compute-service-id-versions.mdx @@ -1,6 +1,6 @@ --- title: Create compute version -description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Creates a new compute version under the specified compute service. Returns a pre-signed upload URL for the artifact unless `skipCodeUpload` is set (which forks the latest version''s artifact). Environment variables are merged with the previous version''s variables when one exists.' +description: "Experimental endpoint: this API is in active development and may change at any time without notice. Creates a new compute version under the specified compute service. Returns a pre-signed upload URL for the artifact unless `skipCodeUpload` is set (which forks the latest version's artifact). Environment variables are merged with the previous version's variables when one exists." full: true _openapi: method: POST @@ -12,10 +12,10 @@ _openapi: ⚠️ Experimental endpoint: this API is in active development and may change at any time without notice. ⚠️ Creates a new compute version under the specified compute service. Returns a pre-signed upload URL for the artifact unless `skipCodeUpload` is set (which forks the latest version's artifact). Environment variables are merged with the previous version's variables when one exists. - path: '/v1/compute-services/{computeServiceId}/versions' -url: '/management-api/endpoints/[experimental]/post-compute-services-by-compute-service-id-versions' -metaTitle: 'POST /v1/compute-services/{computeServiceId}/versions | Create compute version' -metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Creates a new compute version under the specified compute service. Returns a pre-signed upload URL for the artifact unless `skipCodeUpload` is set (which forks the latest version''s artifact). Environment variables are merged with the previous version''s variables when one exists.' + path: "/v1/compute-services/{computeServiceId}/versions" +url: "/management-api/endpoints/[experimental]/post-compute-services-by-compute-service-id-versions" +metaTitle: "POST /v1/compute-services/{computeServiceId}/versions | Create compute version" +metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Creates a new compute version under the specified compute service. Returns a pre-signed upload URL for the artifact unless `skipCodeUpload` is set (which forks the latest version's artifact). Environment variables are merged with the previous version's variables when one exists." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,4 +24,7 @@ metaDescription: 'Management API: Experimental endpoint: this API is in active d Creates a new compute version under the specified compute service. Returns a pre-signed upload URL for the artifact unless `skipCodeUpload` is set (which forks the latest version's artifact). Environment variables are merged with the previous version's variables when one exists. - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services-versions-by-version-id-start.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services-versions-by-version-id-start.mdx index b35c2e3506..f3c37a12ae 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services-versions-by-version-id-start.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services-versions-by-version-id-start.mdx @@ -1,6 +1,6 @@ --- title: Start compute version -description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Requests VM creation and startup for the compute version. The artifact must be uploaded before calling this endpoint. Returns a preview domain that becomes reachable once the VM is running. Poll the status endpoint until `running` is reached.' +description: "Experimental endpoint: this API is in active development and may change at any time without notice. Requests VM creation and startup for the compute version. The artifact must be uploaded before calling this endpoint. Returns a preview domain that becomes reachable once the VM is running. Poll the status endpoint until `running` is reached." full: true _openapi: method: POST @@ -12,10 +12,10 @@ _openapi: ⚠️ Experimental endpoint: this API is in active development and may change at any time without notice. ⚠️ Requests VM creation and startup for the compute version. The artifact must be uploaded before calling this endpoint. Returns a preview domain that becomes reachable once the VM is running. Poll the status endpoint until `running` is reached. - path: '/v1/compute-services/versions/{versionId}/start' -url: '/management-api/endpoints/[experimental]/post-compute-services-versions-by-version-id-start' -metaTitle: 'POST /v1/compute-services/versions/{versionId}/start | Start compute version' -metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Requests VM creation and startup for the compute version. The artifact must be uploaded before calling this endpoint. Returns a preview domain that becomes reachable once the VM is running. Poll the status endpoint until `running` is reached.' + path: "/v1/compute-services/versions/{versionId}/start" +url: "/management-api/endpoints/[experimental]/post-compute-services-versions-by-version-id-start" +metaTitle: "POST /v1/compute-services/versions/{versionId}/start | Start compute version" +metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Requests VM creation and startup for the compute version. The artifact must be uploaded before calling this endpoint. Returns a preview domain that becomes reachable once the VM is running. Poll the status endpoint until `running` is reached." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,4 +24,7 @@ metaDescription: 'Management API: Experimental endpoint: this API is in active d Requests VM creation and startup for the compute version. The artifact must be uploaded before calling this endpoint. Returns a preview domain that becomes reachable once the VM is running. Poll the status endpoint until `running` is reached. - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services-versions-by-version-id-stop.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services-versions-by-version-id-stop.mdx index b19549b5ff..cea7ac9e89 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services-versions-by-version-id-stop.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services-versions-by-version-id-stop.mdx @@ -1,6 +1,6 @@ --- title: Stop compute version -description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Requests VM shutdown for the compute version. The version record and metadata are retained. Poll the status endpoint until `stopped` is reached.' +description: "Experimental endpoint: this API is in active development and may change at any time without notice. Requests VM shutdown for the compute version. The version record and metadata are retained. Poll the status endpoint until `stopped` is reached." full: true _openapi: method: POST @@ -12,10 +12,10 @@ _openapi: ⚠️ Experimental endpoint: this API is in active development and may change at any time without notice. ⚠️ Requests VM shutdown for the compute version. The version record and metadata are retained. Poll the status endpoint until `stopped` is reached. - path: '/v1/compute-services/versions/{versionId}/stop' -url: '/management-api/endpoints/[experimental]/post-compute-services-versions-by-version-id-stop' -metaTitle: 'POST /v1/compute-services/versions/{versionId}/stop | Stop compute version' -metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Requests VM shutdown for the compute version. The version record and metadata are retained. Poll the status endpoint until `stopped` is reached.' + path: "/v1/compute-services/versions/{versionId}/stop" +url: "/management-api/endpoints/[experimental]/post-compute-services-versions-by-version-id-stop" +metaTitle: "POST /v1/compute-services/versions/{versionId}/stop | Stop compute version" +metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Requests VM shutdown for the compute version. The version record and metadata are retained. Poll the status endpoint until `stopped` is reached." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,4 +24,7 @@ metaDescription: 'Management API: Experimental endpoint: this API is in active d Requests VM shutdown for the compute version. The version record and metadata are retained. Poll the status endpoint until `stopped` is reached. - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services.mdx index 841863e3d6..555d9ed8f2 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services.mdx @@ -1,6 +1,6 @@ --- title: Create compute service -description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Creates a new compute service under the specified project. The `projectId` is required in the request body. The service is placed in the given region (or the default region if omitted).' +description: "Experimental endpoint: this API is in active development and may change at any time without notice. Creates a new compute service under the specified project. The `projectId` is required in the request body. The service is placed in the given region (or the default region if omitted)." full: true _openapi: method: POST @@ -13,9 +13,9 @@ _openapi: Creates a new compute service under the specified project. The `projectId` is required in the request body. The service is placed in the given region (or the default region if omitted). path: /v1/compute-services -url: '/management-api/endpoints/[experimental]/post-compute-services' +url: "/management-api/endpoints/[experimental]/post-compute-services" metaTitle: POST /v1/compute-services | Create compute service -metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Creates a new compute service under the specified project. The `projectId` is required in the request body. The service is placed in the given region (or the default region if omitted).' +metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Creates a new compute service under the specified project. The `projectId` is required in the request body. The service is placed in the given region (or the default region if omitted)." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,4 +24,7 @@ metaDescription: 'Management API: Experimental endpoint: this API is in active d Creates a new compute service under the specified project. The `projectId` is required in the request body. The service is placed in the given region (or the default region if omitted). - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/post-projects-by-project-id-compute-services.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/post-projects-by-project-id-compute-services.mdx index 117ec206f9..f1ee5e532a 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/post-projects-by-project-id-compute-services.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/post-projects-by-project-id-compute-services.mdx @@ -1,6 +1,6 @@ --- title: Create compute service -description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Creates a new compute service under the specified project. The service is placed in the given region (or the default region if omitted).' +description: "Experimental endpoint: this API is in active development and may change at any time without notice. Creates a new compute service under the specified project. The service is placed in the given region (or the default region if omitted)." full: true _openapi: method: POST @@ -12,10 +12,10 @@ _openapi: ⚠️ Experimental endpoint: this API is in active development and may change at any time without notice. ⚠️ Creates a new compute service under the specified project. The service is placed in the given region (or the default region if omitted). - path: '/v1/projects/{projectId}/compute-services' -url: '/management-api/endpoints/[experimental]/post-projects-by-project-id-compute-services' -metaTitle: 'POST /v1/projects/{projectId}/compute-services | Create compute service' -metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Creates a new compute service under the specified project. The service is placed in the given region (or the default region if omitted).' + path: "/v1/projects/{projectId}/compute-services" +url: "/management-api/endpoints/[experimental]/post-projects-by-project-id-compute-services" +metaTitle: "POST /v1/projects/{projectId}/compute-services | Create compute service" +metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Creates a new compute service under the specified project. The service is placed in the given region (or the default region if omitted)." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,4 +24,7 @@ metaDescription: 'Management API: Experimental endpoint: this API is in active d Creates a new compute service under the specified project. The service is placed in the given region (or the default region if omitted). - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/post-versions-by-version-id-start.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/post-versions-by-version-id-start.mdx index 30504cd12a..b3ea1c5668 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/post-versions-by-version-id-start.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/post-versions-by-version-id-start.mdx @@ -1,6 +1,6 @@ --- title: Start compute version -description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Requests VM creation and startup for the compute version. The artifact must be uploaded before calling this endpoint. Returns a preview domain that becomes reachable once the VM is running. Poll the status endpoint until `running` is reached.' +description: "Experimental endpoint: this API is in active development and may change at any time without notice. Requests VM creation and startup for the compute version. The artifact must be uploaded before calling this endpoint. Returns a preview domain that becomes reachable once the VM is running. Poll the status endpoint until `running` is reached." full: true _openapi: method: POST @@ -12,10 +12,10 @@ _openapi: ⚠️ Experimental endpoint: this API is in active development and may change at any time without notice. ⚠️ Requests VM creation and startup for the compute version. The artifact must be uploaded before calling this endpoint. Returns a preview domain that becomes reachable once the VM is running. Poll the status endpoint until `running` is reached. - path: '/v1/versions/{versionId}/start' -url: '/management-api/endpoints/[experimental]/post-versions-by-version-id-start' -metaTitle: 'POST /v1/versions/{versionId}/start | Start compute version' -metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Requests VM creation and startup for the compute version. The artifact must be uploaded before calling this endpoint. Returns a preview domain that becomes reachable once the VM is running. Poll the status endpoint until `running` is reached.' + path: "/v1/versions/{versionId}/start" +url: "/management-api/endpoints/[experimental]/post-versions-by-version-id-start" +metaTitle: "POST /v1/versions/{versionId}/start | Start compute version" +metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Requests VM creation and startup for the compute version. The artifact must be uploaded before calling this endpoint. Returns a preview domain that becomes reachable once the VM is running. Poll the status endpoint until `running` is reached." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,4 +24,7 @@ metaDescription: 'Management API: Experimental endpoint: this API is in active d Requests VM creation and startup for the compute version. The artifact must be uploaded before calling this endpoint. Returns a preview domain that becomes reachable once the VM is running. Poll the status endpoint until `running` is reached. - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/post-versions-by-version-id-stop.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/post-versions-by-version-id-stop.mdx index 0691200326..debd49c488 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/post-versions-by-version-id-stop.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/post-versions-by-version-id-stop.mdx @@ -1,6 +1,6 @@ --- title: Stop compute version -description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Requests VM shutdown for the compute version. The version record and metadata are retained. Poll the status endpoint until `stopped` is reached.' +description: "Experimental endpoint: this API is in active development and may change at any time without notice. Requests VM shutdown for the compute version. The version record and metadata are retained. Poll the status endpoint until `stopped` is reached." full: true _openapi: method: POST @@ -12,10 +12,10 @@ _openapi: ⚠️ Experimental endpoint: this API is in active development and may change at any time without notice. ⚠️ Requests VM shutdown for the compute version. The version record and metadata are retained. Poll the status endpoint until `stopped` is reached. - path: '/v1/versions/{versionId}/stop' -url: '/management-api/endpoints/[experimental]/post-versions-by-version-id-stop' -metaTitle: 'POST /v1/versions/{versionId}/stop | Stop compute version' -metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Requests VM shutdown for the compute version. The version record and metadata are retained. Poll the status endpoint until `stopped` is reached.' + path: "/v1/versions/{versionId}/stop" +url: "/management-api/endpoints/[experimental]/post-versions-by-version-id-stop" +metaTitle: "POST /v1/versions/{versionId}/stop | Stop compute version" +metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Requests VM shutdown for the compute version. The version record and metadata are retained. Poll the status endpoint until `stopped` is reached." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,4 +24,7 @@ metaDescription: 'Management API: Experimental endpoint: this API is in active d Requests VM shutdown for the compute version. The version record and metadata are retained. Poll the status endpoint until `stopped` is reached. - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/post-versions.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/post-versions.mdx index 58565e656d..6f5e9b58f4 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/post-versions.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/post-versions.mdx @@ -1,6 +1,6 @@ --- title: Create compute version -description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Creates a new compute version under the specified compute service. The `computeServiceId` is required in the request body. Returns a pre-signed upload URL for the artifact unless `skipCodeUpload` is set. Environment variables are merged with the previous version''s variables when one exists.' +description: "Experimental endpoint: this API is in active development and may change at any time without notice. Creates a new compute version under the specified compute service. The `computeServiceId` is required in the request body. Returns a pre-signed upload URL for the artifact unless `skipCodeUpload` is set. Environment variables are merged with the previous version's variables when one exists." full: true _openapi: method: POST @@ -13,9 +13,9 @@ _openapi: Creates a new compute version under the specified compute service. The `computeServiceId` is required in the request body. Returns a pre-signed upload URL for the artifact unless `skipCodeUpload` is set. Environment variables are merged with the previous version's variables when one exists. path: /v1/versions -url: '/management-api/endpoints/[experimental]/post-versions' +url: "/management-api/endpoints/[experimental]/post-versions" metaTitle: POST /v1/versions | Create compute version -metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Creates a new compute version under the specified compute service. The `computeServiceId` is required in the request body. Returns a pre-signed upload URL for the artifact unless `skipCodeUpload` is set. Environment variables are merged with the previous version''s variables when one exists.' +metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Creates a new compute version under the specified compute service. The `computeServiceId` is required in the request body. Returns a pre-signed upload URL for the artifact unless `skipCodeUpload` is set. Environment variables are merged with the previous version's variables when one exists." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,4 +24,4 @@ metaDescription: 'Management API: Experimental endpoint: this API is in active d Creates a new compute version under the specified compute service. The `computeServiceId` is required in the request body. Returns a pre-signed upload URL for the artifact unless `skipCodeUpload` is set. Environment variables are merged with the previous version's variables when one exists. - + diff --git a/apps/docs/content/docs/management-api/endpoints/connections/delete-connections-by-id.mdx b/apps/docs/content/docs/management-api/endpoints/connections/delete-connections-by-id.mdx index e005accaf6..ee05fd8957 100644 --- a/apps/docs/content/docs/management-api/endpoints/connections/delete-connections-by-id.mdx +++ b/apps/docs/content/docs/management-api/endpoints/connections/delete-connections-by-id.mdx @@ -9,14 +9,17 @@ _openapi: headings: [] contents: - content: Deletes the connection with the given ID. - path: '/v1/connections/{id}' + path: "/v1/connections/{id}" url: /management-api/endpoints/connections/delete-connections-by-id -metaTitle: 'DELETE /v1/connections/{id} | Delete connection' -metaDescription: 'Management API: Deletes the connection with the given ID.' +metaTitle: "DELETE /v1/connections/{id} | Delete connection" +metaDescription: "Management API: Deletes the connection with the given ID." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Deletes the connection with the given ID. - + diff --git a/apps/docs/content/docs/management-api/endpoints/connections/get-connections-by-id.mdx b/apps/docs/content/docs/management-api/endpoints/connections/get-connections-by-id.mdx index 1cc3cd0e93..fa08da2fc0 100644 --- a/apps/docs/content/docs/management-api/endpoints/connections/get-connections-by-id.mdx +++ b/apps/docs/content/docs/management-api/endpoints/connections/get-connections-by-id.mdx @@ -9,14 +9,17 @@ _openapi: headings: [] contents: - content: Returns the connection with the given ID. - path: '/v1/connections/{id}' + path: "/v1/connections/{id}" url: /management-api/endpoints/connections/get-connections-by-id -metaTitle: 'GET /v1/connections/{id} | Get connection' -metaDescription: 'Management API: Returns the connection with the given ID.' +metaTitle: "GET /v1/connections/{id} | Get connection" +metaDescription: "Management API: Returns the connection with the given ID." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Returns the connection with the given ID. - + diff --git a/apps/docs/content/docs/management-api/endpoints/connections/get-connections.mdx b/apps/docs/content/docs/management-api/endpoints/connections/get-connections.mdx index e701da7394..d051330cc7 100644 --- a/apps/docs/content/docs/management-api/endpoints/connections/get-connections.mdx +++ b/apps/docs/content/docs/management-api/endpoints/connections/get-connections.mdx @@ -1,6 +1,6 @@ --- title: List connections -description: 'Returns all connections the actor has access to, with optional database filter.' +description: "Returns all connections the actor has access to, with optional database filter." full: true _openapi: method: GET @@ -8,15 +8,15 @@ _openapi: structuredData: headings: [] contents: - - content: 'Returns all connections the actor has access to, with optional database filter.' + - content: "Returns all connections the actor has access to, with optional database filter." path: /v1/connections url: /management-api/endpoints/connections/get-connections metaTitle: GET /v1/connections | List connections -metaDescription: 'Management API: Returns all connections the actor has access to, with optional database filter.' +metaDescription: "Management API: Returns all connections the actor has access to, with optional database filter." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Returns all connections the actor has access to, with optional database filter. - + diff --git a/apps/docs/content/docs/management-api/endpoints/connections/post-connections-by-id-rotate.mdx b/apps/docs/content/docs/management-api/endpoints/connections/post-connections-by-id-rotate.mdx index ae7187406f..aba8b4b942 100644 --- a/apps/docs/content/docs/management-api/endpoints/connections/post-connections-by-id-rotate.mdx +++ b/apps/docs/content/docs/management-api/endpoints/connections/post-connections-by-id-rotate.mdx @@ -9,14 +9,17 @@ _openapi: headings: [] contents: - content: Generates new credentials for the connection with the given ID. Revocation of the previous credentials is best-effort. - path: '/v1/connections/{id}/rotate' + path: "/v1/connections/{id}/rotate" url: /management-api/endpoints/connections/post-connections-by-id-rotate -metaTitle: 'POST /v1/connections/{id}/rotate | Rotate connection credentials' -metaDescription: 'Management API: Generates new credentials for the connection with the given ID. Revocation of the previous credentials is best-effort.' +metaTitle: "POST /v1/connections/{id}/rotate | Rotate connection credentials" +metaDescription: "Management API: Generates new credentials for the connection with the given ID. Revocation of the previous credentials is best-effort." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Generates new credentials for the connection with the given ID. Revocation of the previous credentials is best-effort. - + diff --git a/apps/docs/content/docs/management-api/endpoints/connections/post-connections.mdx b/apps/docs/content/docs/management-api/endpoints/connections/post-connections.mdx index bf3c0b7e82..3fff7112e2 100644 --- a/apps/docs/content/docs/management-api/endpoints/connections/post-connections.mdx +++ b/apps/docs/content/docs/management-api/endpoints/connections/post-connections.mdx @@ -12,11 +12,11 @@ _openapi: path: /v1/connections url: /management-api/endpoints/connections/post-connections metaTitle: POST /v1/connections | Create connection -metaDescription: 'Management API: Creates a new connection for the specified database.' +metaDescription: "Management API: Creates a new connection for the specified database." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Creates a new connection for the specified database. - + diff --git a/apps/docs/content/docs/management-api/endpoints/database-backups/get-databases-by-database-id-backups.mdx b/apps/docs/content/docs/management-api/endpoints/database-backups/get-databases-by-database-id-backups.mdx index fcb96f1a67..ab1e553261 100644 --- a/apps/docs/content/docs/management-api/endpoints/database-backups/get-databases-by-database-id-backups.mdx +++ b/apps/docs/content/docs/management-api/endpoints/database-backups/get-databases-by-database-id-backups.mdx @@ -9,14 +9,17 @@ _openapi: headings: [] contents: - content: Returns backups for the specified database. - path: '/v1/databases/{databaseId}/backups' + path: "/v1/databases/{databaseId}/backups" url: /management-api/endpoints/database-backups/get-databases-by-database-id-backups -metaTitle: 'GET /v1/databases/{databaseId}/backups | Get list of backups' -metaDescription: 'Management API: Returns backups for the specified database.' +metaTitle: "GET /v1/databases/{databaseId}/backups | Get list of backups" +metaDescription: "Management API: Returns backups for the specified database." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Returns backups for the specified database. - + diff --git a/apps/docs/content/docs/management-api/endpoints/database-usage/get-databases-by-database-id-usage.mdx b/apps/docs/content/docs/management-api/endpoints/database-usage/get-databases-by-database-id-usage.mdx index e03e0cfe1a..312d83c635 100644 --- a/apps/docs/content/docs/management-api/endpoints/database-usage/get-databases-by-database-id-usage.mdx +++ b/apps/docs/content/docs/management-api/endpoints/database-usage/get-databases-by-database-id-usage.mdx @@ -9,14 +9,17 @@ _openapi: headings: [] contents: - content: Returns usage metrics for the specified database. - path: '/v1/databases/{databaseId}/usage' + path: "/v1/databases/{databaseId}/usage" url: /management-api/endpoints/database-usage/get-databases-by-database-id-usage -metaTitle: 'GET /v1/databases/{databaseId}/usage | Get database usage metrics' -metaDescription: 'Management API: Returns usage metrics for the specified database.' +metaTitle: "GET /v1/databases/{databaseId}/usage | Get database usage metrics" +metaDescription: "Management API: Returns usage metrics for the specified database." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Returns usage metrics for the specified database. - + diff --git a/apps/docs/content/docs/management-api/endpoints/databases-connections/get-databases-by-database-id-connections.mdx b/apps/docs/content/docs/management-api/endpoints/databases-connections/get-databases-by-database-id-connections.mdx index 632f721123..7980983561 100644 --- a/apps/docs/content/docs/management-api/endpoints/databases-connections/get-databases-by-database-id-connections.mdx +++ b/apps/docs/content/docs/management-api/endpoints/databases-connections/get-databases-by-database-id-connections.mdx @@ -9,14 +9,17 @@ _openapi: headings: [] contents: - content: Returns all connections for the given database. - path: '/v1/databases/{databaseId}/connections' + path: "/v1/databases/{databaseId}/connections" url: /management-api/endpoints/databases-connections/get-databases-by-database-id-connections -metaTitle: 'GET /v1/databases/{databaseId}/connections | Get list of database connections' -metaDescription: 'Management API: Returns all connections for the given database.' +metaTitle: "GET /v1/databases/{databaseId}/connections | Get list of database connections" +metaDescription: "Management API: Returns all connections for the given database." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Returns all connections for the given database. - + diff --git a/apps/docs/content/docs/management-api/endpoints/databases-connections/post-databases-by-database-id-connections.mdx b/apps/docs/content/docs/management-api/endpoints/databases-connections/post-databases-by-database-id-connections.mdx index 50d0f41307..9e1849c931 100644 --- a/apps/docs/content/docs/management-api/endpoints/databases-connections/post-databases-by-database-id-connections.mdx +++ b/apps/docs/content/docs/management-api/endpoints/databases-connections/post-databases-by-database-id-connections.mdx @@ -9,14 +9,17 @@ _openapi: headings: [] contents: - content: Creates a new connection string for the given database. - path: '/v1/databases/{databaseId}/connections' + path: "/v1/databases/{databaseId}/connections" url: /management-api/endpoints/databases-connections/post-databases-by-database-id-connections -metaTitle: 'POST /v1/databases/{databaseId}/connections | Create database connection string' -metaDescription: 'Management API: Creates a new connection string for the given database.' +metaTitle: "POST /v1/databases/{databaseId}/connections | Create database connection string" +metaDescription: "Management API: Creates a new connection string for the given database." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Creates a new connection string for the given database. - + diff --git a/apps/docs/content/docs/management-api/endpoints/databases/delete-databases-by-database-id.mdx b/apps/docs/content/docs/management-api/endpoints/databases/delete-databases-by-database-id.mdx index 2f9342b51c..094ec72957 100644 --- a/apps/docs/content/docs/management-api/endpoints/databases/delete-databases-by-database-id.mdx +++ b/apps/docs/content/docs/management-api/endpoints/databases/delete-databases-by-database-id.mdx @@ -9,14 +9,17 @@ _openapi: headings: [] contents: - content: Deletes the database with the given ID. - path: '/v1/databases/{databaseId}' + path: "/v1/databases/{databaseId}" url: /management-api/endpoints/databases/delete-databases-by-database-id -metaTitle: 'DELETE /v1/databases/{databaseId} | Delete database' -metaDescription: 'Management API: Deletes the database with the given ID.' +metaTitle: "DELETE /v1/databases/{databaseId} | Delete database" +metaDescription: "Management API: Deletes the database with the given ID." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Deletes the database with the given ID. - + diff --git a/apps/docs/content/docs/management-api/endpoints/databases/get-databases-by-database-id.mdx b/apps/docs/content/docs/management-api/endpoints/databases/get-databases-by-database-id.mdx index 47f9a36c54..78b900940b 100644 --- a/apps/docs/content/docs/management-api/endpoints/databases/get-databases-by-database-id.mdx +++ b/apps/docs/content/docs/management-api/endpoints/databases/get-databases-by-database-id.mdx @@ -9,14 +9,17 @@ _openapi: headings: [] contents: - content: Returns the database with the given ID. - path: '/v1/databases/{databaseId}' + path: "/v1/databases/{databaseId}" url: /management-api/endpoints/databases/get-databases-by-database-id -metaTitle: 'GET /v1/databases/{databaseId} | Get database' -metaDescription: 'Management API: Returns the database with the given ID.' +metaTitle: "GET /v1/databases/{databaseId} | Get database" +metaDescription: "Management API: Returns the database with the given ID." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Returns the database with the given ID. - + diff --git a/apps/docs/content/docs/management-api/endpoints/databases/get-databases.mdx b/apps/docs/content/docs/management-api/endpoints/databases/get-databases.mdx index 72c90ced63..4cf3c009f6 100644 --- a/apps/docs/content/docs/management-api/endpoints/databases/get-databases.mdx +++ b/apps/docs/content/docs/management-api/endpoints/databases/get-databases.mdx @@ -12,11 +12,11 @@ _openapi: path: /v1/databases url: /management-api/endpoints/databases/get-databases metaTitle: GET /v1/databases | List databases -metaDescription: 'Management API: Returns all databases the token has access to. Optionally filter by project ID.' +metaDescription: "Management API: Returns all databases the token has access to. Optionally filter by project ID." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Returns all databases the token has access to. Optionally filter by project ID. - + diff --git a/apps/docs/content/docs/management-api/endpoints/databases/get-projects-by-project-id-databases.mdx b/apps/docs/content/docs/management-api/endpoints/databases/get-projects-by-project-id-databases.mdx index 8e05672345..854350aebb 100644 --- a/apps/docs/content/docs/management-api/endpoints/databases/get-projects-by-project-id-databases.mdx +++ b/apps/docs/content/docs/management-api/endpoints/databases/get-projects-by-project-id-databases.mdx @@ -9,14 +9,17 @@ _openapi: headings: [] contents: - content: Returns databases for the given project. - path: '/v1/projects/{projectId}/databases' + path: "/v1/projects/{projectId}/databases" url: /management-api/endpoints/databases/get-projects-by-project-id-databases -metaTitle: 'GET /v1/projects/{projectId}/databases | Get list of databases' -metaDescription: 'Management API: Returns databases for the given project.' +metaTitle: "GET /v1/projects/{projectId}/databases | Get list of databases" +metaDescription: "Management API: Returns databases for the given project." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Returns databases for the given project. - + diff --git a/apps/docs/content/docs/management-api/endpoints/databases/patch-databases-by-database-id.mdx b/apps/docs/content/docs/management-api/endpoints/databases/patch-databases-by-database-id.mdx index 4a0712b314..d7bede3d4e 100644 --- a/apps/docs/content/docs/management-api/endpoints/databases/patch-databases-by-database-id.mdx +++ b/apps/docs/content/docs/management-api/endpoints/databases/patch-databases-by-database-id.mdx @@ -9,14 +9,17 @@ _openapi: headings: [] contents: - content: Updates the database with the given ID. - path: '/v1/databases/{databaseId}' + path: "/v1/databases/{databaseId}" url: /management-api/endpoints/databases/patch-databases-by-database-id -metaTitle: 'PATCH /v1/databases/{databaseId} | Update database' -metaDescription: 'Management API: Updates the database with the given ID.' +metaTitle: "PATCH /v1/databases/{databaseId} | Update database" +metaDescription: "Management API: Updates the database with the given ID." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Updates the database with the given ID. - + diff --git a/apps/docs/content/docs/management-api/endpoints/databases/post-databases-by-target-database-id-restore.mdx b/apps/docs/content/docs/management-api/endpoints/databases/post-databases-by-target-database-id-restore.mdx index 86ec1b0ab5..aeb2605265 100644 --- a/apps/docs/content/docs/management-api/endpoints/databases/post-databases-by-target-database-id-restore.mdx +++ b/apps/docs/content/docs/management-api/endpoints/databases/post-databases-by-target-database-id-restore.mdx @@ -1,6 +1,6 @@ --- title: Restore database (destructive) -description: '**Destructive operation** — this immediately and irreversibly overwrites all data in the target database with the contents of the specified backup. Any data written since the backup was taken will be lost. Ensure you have a recent backup of the target database before proceeding. Replaces the data in an existing database from a backup. Connections and credentials are preserved — only the data layer is replaced.' +description: "**Destructive operation** — this immediately and irreversibly overwrites all data in the target database with the contents of the specified backup. Any data written since the backup was taken will be lost. Ensure you have a recent backup of the target database before proceeding. Replaces the data in an existing database from a backup. Connections and credentials are preserved — only the data layer is replaced." full: true _openapi: method: POST @@ -12,10 +12,10 @@ _openapi: ⚠️ **Destructive operation** — this immediately and irreversibly overwrites all data in the target database with the contents of the specified backup. Any data written since the backup was taken will be lost. Ensure you have a recent backup of the target database before proceeding. Replaces the data in an existing database from a backup. Connections and credentials are preserved — only the data layer is replaced. - path: '/v1/databases/{targetDatabaseId}/restore' + path: "/v1/databases/{targetDatabaseId}/restore" url: /management-api/endpoints/databases/post-databases-by-target-database-id-restore -metaTitle: 'POST /v1/databases/{targetDatabaseId}/restore | Restore database (destructive)' -metaDescription: 'Management API: **Destructive operation** — this immediately and irreversibly overwrites all data in the target database with the contents of the specified backup. Any data written since the backup was taken will be lost. Ensure you have a recent backup of the target database before proceeding. Replaces the data in an existing database from a backup. Connections and credentials are preserved — only the data layer is replaced.' +metaTitle: "POST /v1/databases/{targetDatabaseId}/restore | Restore database (destructive)" +metaDescription: "Management API: **Destructive operation** — this immediately and irreversibly overwrites all data in the target database with the contents of the specified backup. Any data written since the backup was taken will be lost. Ensure you have a recent backup of the target database before proceeding. Replaces the data in an existing database from a backup. Connections and credentials are preserved — only the data layer is replaced." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,4 +24,7 @@ metaDescription: 'Management API: **Destructive operation** — this immediately Replaces the data in an existing database from a backup. Connections and credentials are preserved — only the data layer is replaced. - + diff --git a/apps/docs/content/docs/management-api/endpoints/databases/post-databases.mdx b/apps/docs/content/docs/management-api/endpoints/databases/post-databases.mdx index 584afa8ebc..d82697e1c6 100644 --- a/apps/docs/content/docs/management-api/endpoints/databases/post-databases.mdx +++ b/apps/docs/content/docs/management-api/endpoints/databases/post-databases.mdx @@ -12,11 +12,11 @@ _openapi: path: /v1/databases url: /management-api/endpoints/databases/post-databases metaTitle: POST /v1/databases | Create database -metaDescription: 'Management API: Creates a new database in the specified project.' +metaDescription: "Management API: Creates a new database in the specified project." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Creates a new database in the specified project. - + diff --git a/apps/docs/content/docs/management-api/endpoints/databases/post-projects-by-project-id-databases.mdx b/apps/docs/content/docs/management-api/endpoints/databases/post-projects-by-project-id-databases.mdx index 5145461545..0926253fa6 100644 --- a/apps/docs/content/docs/management-api/endpoints/databases/post-projects-by-project-id-databases.mdx +++ b/apps/docs/content/docs/management-api/endpoints/databases/post-projects-by-project-id-databases.mdx @@ -9,14 +9,17 @@ _openapi: headings: [] contents: - content: Creates a new database for the given project. - path: '/v1/projects/{projectId}/databases' + path: "/v1/projects/{projectId}/databases" url: /management-api/endpoints/databases/post-projects-by-project-id-databases -metaTitle: 'POST /v1/projects/{projectId}/databases | Create database' -metaDescription: 'Management API: Creates a new database for the given project.' +metaTitle: "POST /v1/projects/{projectId}/databases | Create database" +metaDescription: "Management API: Creates a new database for the given project." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Creates a new database for the given project. - + diff --git a/apps/docs/content/docs/management-api/endpoints/integrations/delete-integrations-by-id.mdx b/apps/docs/content/docs/management-api/endpoints/integrations/delete-integrations-by-id.mdx index 1834d70d96..62dae6e502 100644 --- a/apps/docs/content/docs/management-api/endpoints/integrations/delete-integrations-by-id.mdx +++ b/apps/docs/content/docs/management-api/endpoints/integrations/delete-integrations-by-id.mdx @@ -9,14 +9,17 @@ _openapi: headings: [] contents: - content: Revokes the integration tokens by integration ID. - path: '/v1/integrations/{id}' + path: "/v1/integrations/{id}" url: /management-api/endpoints/integrations/delete-integrations-by-id -metaTitle: 'DELETE /v1/integrations/{id} | Delete integration' -metaDescription: 'Management API: Revokes the integration tokens by integration ID.' +metaTitle: "DELETE /v1/integrations/{id} | Delete integration" +metaDescription: "Management API: Revokes the integration tokens by integration ID." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Revokes the integration tokens by integration ID. - + diff --git a/apps/docs/content/docs/management-api/endpoints/integrations/delete-workspaces-by-workspace-id-integrations-by-client-id.mdx b/apps/docs/content/docs/management-api/endpoints/integrations/delete-workspaces-by-workspace-id-integrations-by-client-id.mdx index 5e01a165b3..94aeb02b00 100644 --- a/apps/docs/content/docs/management-api/endpoints/integrations/delete-workspaces-by-workspace-id-integrations-by-client-id.mdx +++ b/apps/docs/content/docs/management-api/endpoints/integrations/delete-workspaces-by-workspace-id-integrations-by-client-id.mdx @@ -9,14 +9,17 @@ _openapi: headings: [] contents: - content: Revokes the integration tokens with the given client ID. - path: '/v1/workspaces/{workspaceId}/integrations/{clientId}' + path: "/v1/workspaces/{workspaceId}/integrations/{clientId}" url: /management-api/endpoints/integrations/delete-workspaces-by-workspace-id-integrations-by-client-id -metaTitle: 'DELETE /v1/workspaces/{workspaceId}/integrations/{clientId} | Revoke integration tokens' -metaDescription: 'Management API: Revokes the integration tokens with the given client ID.' +metaTitle: "DELETE /v1/workspaces/{workspaceId}/integrations/{clientId} | Revoke integration tokens" +metaDescription: "Management API: Revokes the integration tokens with the given client ID." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Revokes the integration tokens with the given client ID. - + diff --git a/apps/docs/content/docs/management-api/endpoints/integrations/get-integrations-by-id.mdx b/apps/docs/content/docs/management-api/endpoints/integrations/get-integrations-by-id.mdx index eb5ae6cc2e..854e6fe8f8 100644 --- a/apps/docs/content/docs/management-api/endpoints/integrations/get-integrations-by-id.mdx +++ b/apps/docs/content/docs/management-api/endpoints/integrations/get-integrations-by-id.mdx @@ -9,14 +9,17 @@ _openapi: headings: [] contents: - content: Returns a single integration by its ID. - path: '/v1/integrations/{id}' + path: "/v1/integrations/{id}" url: /management-api/endpoints/integrations/get-integrations-by-id -metaTitle: 'GET /v1/integrations/{id} | Get integration by ID' -metaDescription: 'Management API: Returns a single integration by its ID.' +metaTitle: "GET /v1/integrations/{id} | Get integration by ID" +metaDescription: "Management API: Returns a single integration by its ID." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Returns a single integration by its ID. - + diff --git a/apps/docs/content/docs/management-api/endpoints/integrations/get-integrations.mdx b/apps/docs/content/docs/management-api/endpoints/integrations/get-integrations.mdx index d5a9a79bb8..314d5d340a 100644 --- a/apps/docs/content/docs/management-api/endpoints/integrations/get-integrations.mdx +++ b/apps/docs/content/docs/management-api/endpoints/integrations/get-integrations.mdx @@ -12,11 +12,11 @@ _openapi: path: /v1/integrations url: /management-api/endpoints/integrations/get-integrations metaTitle: GET /v1/integrations | Get list of integrations -metaDescription: 'Management API: Returns integrations filtered by workspace ID.' +metaDescription: "Management API: Returns integrations filtered by workspace ID." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Returns integrations filtered by workspace ID. - + diff --git a/apps/docs/content/docs/management-api/endpoints/integrations/get-workspaces-by-workspace-id-integrations.mdx b/apps/docs/content/docs/management-api/endpoints/integrations/get-workspaces-by-workspace-id-integrations.mdx index 1c0ff8a207..2ae252cc41 100644 --- a/apps/docs/content/docs/management-api/endpoints/integrations/get-workspaces-by-workspace-id-integrations.mdx +++ b/apps/docs/content/docs/management-api/endpoints/integrations/get-workspaces-by-workspace-id-integrations.mdx @@ -9,14 +9,17 @@ _openapi: headings: [] contents: - content: Returns integrations for the given workspace. - path: '/v1/workspaces/{workspaceId}/integrations' + path: "/v1/workspaces/{workspaceId}/integrations" url: /management-api/endpoints/integrations/get-workspaces-by-workspace-id-integrations -metaTitle: 'GET /v1/workspaces/{workspaceId}/integrations | Get list of integrations' -metaDescription: 'Management API: Returns integrations for the given workspace.' +metaTitle: "GET /v1/workspaces/{workspaceId}/integrations | Get list of integrations" +metaDescription: "Management API: Returns integrations for the given workspace." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Returns integrations for the given workspace. - + diff --git a/apps/docs/content/docs/management-api/endpoints/misc/get-regions-accelerate.mdx b/apps/docs/content/docs/management-api/endpoints/misc/get-regions-accelerate.mdx index 53d9be6d61..3f1eec4a6c 100644 --- a/apps/docs/content/docs/management-api/endpoints/misc/get-regions-accelerate.mdx +++ b/apps/docs/content/docs/management-api/endpoints/misc/get-regions-accelerate.mdx @@ -12,11 +12,14 @@ _openapi: path: /v1/regions/accelerate url: /management-api/endpoints/misc/get-regions-accelerate metaTitle: GET /v1/regions/accelerate | Get Prisma Accelerate regions -metaDescription: 'Management API: Returns all available regions for Prisma Accelerate.' +metaDescription: "Management API: Returns all available regions for Prisma Accelerate." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Returns all available regions for Prisma Accelerate. - + diff --git a/apps/docs/content/docs/management-api/endpoints/misc/get-regions-postgres.mdx b/apps/docs/content/docs/management-api/endpoints/misc/get-regions-postgres.mdx index e801a01325..4ba66e0577 100644 --- a/apps/docs/content/docs/management-api/endpoints/misc/get-regions-postgres.mdx +++ b/apps/docs/content/docs/management-api/endpoints/misc/get-regions-postgres.mdx @@ -12,11 +12,14 @@ _openapi: path: /v1/regions/postgres url: /management-api/endpoints/misc/get-regions-postgres metaTitle: GET /v1/regions/postgres | Get Prisma Postgres regions -metaDescription: 'Management API: Returns all available regions for Prisma Postgres.' +metaDescription: "Management API: Returns all available regions for Prisma Postgres." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Returns all available regions for Prisma Postgres. - + diff --git a/apps/docs/content/docs/management-api/endpoints/projects/delete-projects-by-id.mdx b/apps/docs/content/docs/management-api/endpoints/projects/delete-projects-by-id.mdx index 55c911bba4..b25be880ac 100644 --- a/apps/docs/content/docs/management-api/endpoints/projects/delete-projects-by-id.mdx +++ b/apps/docs/content/docs/management-api/endpoints/projects/delete-projects-by-id.mdx @@ -9,14 +9,17 @@ _openapi: headings: [] contents: - content: Deletes the project with the given ID. - path: '/v1/projects/{id}' + path: "/v1/projects/{id}" url: /management-api/endpoints/projects/delete-projects-by-id -metaTitle: 'DELETE /v1/projects/{id} | Delete project' -metaDescription: 'Management API: Deletes the project with the given ID.' +metaTitle: "DELETE /v1/projects/{id} | Delete project" +metaDescription: "Management API: Deletes the project with the given ID." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Deletes the project with the given ID. - + diff --git a/apps/docs/content/docs/management-api/endpoints/projects/get-projects-by-id.mdx b/apps/docs/content/docs/management-api/endpoints/projects/get-projects-by-id.mdx index e57b7d3455..efd3af4800 100644 --- a/apps/docs/content/docs/management-api/endpoints/projects/get-projects-by-id.mdx +++ b/apps/docs/content/docs/management-api/endpoints/projects/get-projects-by-id.mdx @@ -9,14 +9,14 @@ _openapi: headings: [] contents: - content: Returns the project with the given ID. - path: '/v1/projects/{id}' + path: "/v1/projects/{id}" url: /management-api/endpoints/projects/get-projects-by-id -metaTitle: 'GET /v1/projects/{id} | Get project' -metaDescription: 'Management API: Returns the project with the given ID.' +metaTitle: "GET /v1/projects/{id} | Get project" +metaDescription: "Management API: Returns the project with the given ID." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Returns the project with the given ID. - + diff --git a/apps/docs/content/docs/management-api/endpoints/projects/get-projects.mdx b/apps/docs/content/docs/management-api/endpoints/projects/get-projects.mdx index 43af5e57b7..9efbb8acc6 100644 --- a/apps/docs/content/docs/management-api/endpoints/projects/get-projects.mdx +++ b/apps/docs/content/docs/management-api/endpoints/projects/get-projects.mdx @@ -12,11 +12,11 @@ _openapi: path: /v1/projects url: /management-api/endpoints/projects/get-projects metaTitle: GET /v1/projects | Get list of projects -metaDescription: 'Management API: Returns the list of projects the token has access to.' +metaDescription: "Management API: Returns the list of projects the token has access to." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Returns the list of projects the token has access to. - + diff --git a/apps/docs/content/docs/management-api/endpoints/projects/patch-projects-by-id.mdx b/apps/docs/content/docs/management-api/endpoints/projects/patch-projects-by-id.mdx index 3bf173a402..5ee6bb3c7d 100644 --- a/apps/docs/content/docs/management-api/endpoints/projects/patch-projects-by-id.mdx +++ b/apps/docs/content/docs/management-api/endpoints/projects/patch-projects-by-id.mdx @@ -9,14 +9,17 @@ _openapi: headings: [] contents: - content: Updates the project with the given ID. - path: '/v1/projects/{id}' + path: "/v1/projects/{id}" url: /management-api/endpoints/projects/patch-projects-by-id -metaTitle: 'PATCH /v1/projects/{id} | Update project' -metaDescription: 'Management API: Updates the project with the given ID.' +metaTitle: "PATCH /v1/projects/{id} | Update project" +metaDescription: "Management API: Updates the project with the given ID." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Updates the project with the given ID. - + diff --git a/apps/docs/content/docs/management-api/endpoints/projects/post-projects-by-id-transfer.mdx b/apps/docs/content/docs/management-api/endpoints/projects/post-projects-by-id-transfer.mdx index 22da5c5155..142d0d9deb 100644 --- a/apps/docs/content/docs/management-api/endpoints/projects/post-projects-by-id-transfer.mdx +++ b/apps/docs/content/docs/management-api/endpoints/projects/post-projects-by-id-transfer.mdx @@ -9,14 +9,17 @@ _openapi: headings: [] contents: - content: Transfer the project with the given ID to the new owner's workspace - path: '/v1/projects/{id}/transfer' + path: "/v1/projects/{id}/transfer" url: /management-api/endpoints/projects/post-projects-by-id-transfer -metaTitle: 'POST /v1/projects/{id}/transfer | Transfer project' -metaDescription: 'Management API: Transfer the project with the given ID to the new owner''s workspace' +metaTitle: "POST /v1/projects/{id}/transfer | Transfer project" +metaDescription: "Management API: Transfer the project with the given ID to the new owner's workspace" --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Transfer the project with the given ID to the new owner's workspace - + diff --git a/apps/docs/content/docs/management-api/endpoints/projects/post-projects.mdx b/apps/docs/content/docs/management-api/endpoints/projects/post-projects.mdx index 28cd0e92d0..7f451cf24c 100644 --- a/apps/docs/content/docs/management-api/endpoints/projects/post-projects.mdx +++ b/apps/docs/content/docs/management-api/endpoints/projects/post-projects.mdx @@ -12,11 +12,11 @@ _openapi: path: /v1/projects url: /management-api/endpoints/projects/post-projects metaTitle: POST /v1/projects | Create project with a postgres database -metaDescription: 'Management API: Creates a new project with a postgres database.' +metaDescription: "Management API: Creates a new project with a postgres database." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Creates a new project with a postgres database. - + diff --git a/apps/docs/content/docs/management-api/endpoints/regions/get-regions.mdx b/apps/docs/content/docs/management-api/endpoints/regions/get-regions.mdx index afb8a502e3..f4fb3615cd 100644 --- a/apps/docs/content/docs/management-api/endpoints/regions/get-regions.mdx +++ b/apps/docs/content/docs/management-api/endpoints/regions/get-regions.mdx @@ -12,11 +12,11 @@ _openapi: path: /v1/regions url: /management-api/endpoints/regions/get-regions metaTitle: GET /v1/regions | Get all regions -metaDescription: 'Management API: Returns all available regions across products. Optionally filter by product.' +metaDescription: "Management API: Returns all available regions across products. Optionally filter by product." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Returns all available regions across products. Optionally filter by product. - + diff --git a/apps/docs/content/docs/management-api/endpoints/workspaces/get-workspaces-by-id.mdx b/apps/docs/content/docs/management-api/endpoints/workspaces/get-workspaces-by-id.mdx index 41a8b9faaa..64e14dc889 100644 --- a/apps/docs/content/docs/management-api/endpoints/workspaces/get-workspaces-by-id.mdx +++ b/apps/docs/content/docs/management-api/endpoints/workspaces/get-workspaces-by-id.mdx @@ -9,14 +9,17 @@ _openapi: headings: [] contents: - content: Returns the workspace with the given ID. - path: '/v1/workspaces/{id}' + path: "/v1/workspaces/{id}" url: /management-api/endpoints/workspaces/get-workspaces-by-id -metaTitle: 'GET /v1/workspaces/{id} | Get workspace' -metaDescription: 'Management API: Returns the workspace with the given ID.' +metaTitle: "GET /v1/workspaces/{id} | Get workspace" +metaDescription: "Management API: Returns the workspace with the given ID." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Returns the workspace with the given ID. - + diff --git a/apps/docs/content/docs/management-api/endpoints/workspaces/get-workspaces.mdx b/apps/docs/content/docs/management-api/endpoints/workspaces/get-workspaces.mdx index 45b0590237..f486fc77a4 100644 --- a/apps/docs/content/docs/management-api/endpoints/workspaces/get-workspaces.mdx +++ b/apps/docs/content/docs/management-api/endpoints/workspaces/get-workspaces.mdx @@ -12,11 +12,11 @@ _openapi: path: /v1/workspaces url: /management-api/endpoints/workspaces/get-workspaces metaTitle: GET /v1/workspaces | Get list of workspaces -metaDescription: 'Management API: Returns the list of workspaces the current token can access.' +metaDescription: "Management API: Returns the list of workspaces the current token can access." --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Returns the list of workspaces the current token can access. - + diff --git a/apps/docs/content/docs/management-api/index.mdx b/apps/docs/content/docs/management-api/index.mdx index 685f86eca0..38ca995f25 100644 --- a/apps/docs/content/docs/management-api/index.mdx +++ b/apps/docs/content/docs/management-api/index.mdx @@ -1,8 +1,8 @@ --- title: Management API -description: 'Programmatically manage your Prisma Postgres databases, projects, and workspaces with the Management API' +description: "Programmatically manage your Prisma Postgres databases, projects, and workspaces with the Management API" url: /management-api -metaTitle: 'Prisma Postgres: Management API Reference' +metaTitle: "Prisma Postgres: Management API Reference" metaDescription: Management API reference documentation for Prisma Postgres. --- diff --git a/apps/docs/content/docs/management-api/meta.json b/apps/docs/content/docs/management-api/meta.json index a87f76f4f4..dbb8b2b073 100644 --- a/apps/docs/content/docs/management-api/meta.json +++ b/apps/docs/content/docs/management-api/meta.json @@ -14,4 +14,4 @@ "---Endpoints---", "...endpoints" ] -} \ No newline at end of file +} diff --git a/apps/docs/content/docs/management-api/partner-integration.mdx b/apps/docs/content/docs/management-api/partner-integration.mdx index 2bfa0c9596..698f0e960f 100644 --- a/apps/docs/content/docs/management-api/partner-integration.mdx +++ b/apps/docs/content/docs/management-api/partner-integration.mdx @@ -2,7 +2,7 @@ title: Partner Integration description: Build partner integrations that provision and transfer Prisma Postgres databases to users metaTitle: Partner Integration | Provision & Transfer Databases -metaDescription: 'Build partner integrations with the Management API. Provision Prisma Postgres databases, implement claim flow with OAuth 2.0, transfer projects to user workspaces.' +metaDescription: "Build partner integrations with the Management API. Provision Prisma Postgres databases, implement claim flow with OAuth 2.0, transfer projects to user workspaces." url: /management-api/partner-integration --- diff --git a/apps/docs/content/docs/management-api/sdk.mdx b/apps/docs/content/docs/management-api/sdk.mdx index 47561a1b93..e540b6b941 100644 --- a/apps/docs/content/docs/management-api/sdk.mdx +++ b/apps/docs/content/docs/management-api/sdk.mdx @@ -1,9 +1,9 @@ --- title: SDK -description: 'A TypeScript SDK for the Prisma Data Platform Management API. Use the simple client for direct API access, or the full SDK with built-in OAuth authentication and automatic token refresh' +description: "A TypeScript SDK for the Prisma Data Platform Management API. Use the simple client for direct API access, or the full SDK with built-in OAuth authentication and automatic token refresh" url: /management-api/sdk -metaTitle: 'Prisma Postgres: Management API SDK' -metaDescription: 'A TypeScript SDK for the Prisma Data Platform Management API. Use the simple client for direct API access, or the full SDK with built-in OAuth authentication and automatic token refresh.' +metaTitle: "Prisma Postgres: Management API SDK" +metaDescription: "A TypeScript SDK for the Prisma Data Platform Management API. Use the simple client for direct API access, or the full SDK with built-in OAuth authentication and automatic token refresh." --- ## Overview @@ -264,6 +264,7 @@ const tokenStorage: TokenStorage = { ``` For other environments: + - **VS Code extensions** - Use `context.secrets` for secure storage - **Stateless web servers** - Store PKCE state/verifier in encrypted cookies or a database diff --git a/apps/docs/content/docs/orm/core-concepts/api-patterns.mdx b/apps/docs/content/docs/orm/core-concepts/api-patterns.mdx index 3b368523a7..843e3b8450 100644 --- a/apps/docs/content/docs/orm/core-concepts/api-patterns.mdx +++ b/apps/docs/content/docs/orm/core-concepts/api-patterns.mdx @@ -1,6 +1,6 @@ --- title: API patterns -description: 'How to use Prisma ORM with REST APIs, GraphQL servers, and fullstack frameworks' +description: "How to use Prisma ORM with REST APIs, GraphQL servers, and fullstack frameworks" url: /orm/core-concepts/api-patterns metaTitle: Building REST APIs with Prisma ORM metaDescription: This page gives an overview of the most important things when building REST APIs with Prisma. It shows practical examples and the supported libraries. diff --git a/apps/docs/content/docs/orm/core-concepts/supported-databases/index.mdx b/apps/docs/content/docs/orm/core-concepts/supported-databases/index.mdx index b586b427db..bf5ca772b9 100644 --- a/apps/docs/content/docs/orm/core-concepts/supported-databases/index.mdx +++ b/apps/docs/content/docs/orm/core-concepts/supported-databases/index.mdx @@ -1,6 +1,6 @@ --- title: Overview -description: 'Prisma ORM supports PostgreSQL, MySQL, SQLite, MongoDB, SQL Server, CockroachDB, and serverless databases' +description: "Prisma ORM supports PostgreSQL, MySQL, SQLite, MongoDB, SQL Server, CockroachDB, and serverless databases" url: /orm/core-concepts/supported-databases metaTitle: Databases metaDescription: Prisma ORM supports PostgreSQL, MySQL, SQLite, MongoDB, SQL Server, CockroachDB, and serverless databases' @@ -10,14 +10,14 @@ metaDescription: Prisma ORM supports PostgreSQL, MySQL, SQLite, MongoDB, SQL Ser ### Self-hosted -| Database | Version | -| ----------- | ------- | +| Database | Version | +| ---------------------------------------------------------------- | ------- | | [PostgreSQL](/orm/core-concepts/supported-databases/postgresql) | 9.6+ | -| [MySQL](/orm/core-concepts/supported-databases/mysql) | 5.6+ | -| [MariaDB](/orm/core-concepts/supported-databases/mysql) | 10.0+ | +| [MySQL](/orm/core-concepts/supported-databases/mysql) | 5.6+ | +| [MariaDB](/orm/core-concepts/supported-databases/mysql) | 10.0+ | | [SQL Server](/orm/core-concepts/supported-databases/sql-server) | 2017+ | -| [SQLite](/orm/core-concepts/supported-databases/sqlite) | All | -| [MongoDB](/orm/core-concepts/supported-databases/mongodb) | 4.2+ | +| [SQLite](/orm/core-concepts/supported-databases/sqlite) | All | +| [MongoDB](/orm/core-concepts/supported-databases/mongodb) | 4.2+ | | [CockroachDB](/orm/core-concepts/supported-databases/postgresql) | 21.2.4+ | ### Managed/Serverless diff --git a/apps/docs/content/docs/orm/core-concepts/supported-databases/meta.json b/apps/docs/content/docs/orm/core-concepts/supported-databases/meta.json index c1538d72c2..2a1068f005 100644 --- a/apps/docs/content/docs/orm/core-concepts/supported-databases/meta.json +++ b/apps/docs/content/docs/orm/core-concepts/supported-databases/meta.json @@ -1,12 +1,4 @@ { "title": "Supported databases", - "pages": [ - "index", - "postgresql", - "mysql", - "sqlite", - "sql-server", - "mongodb", - "database-drivers" - ] + "pages": ["index", "postgresql", "mysql", "sqlite", "sql-server", "mongodb", "database-drivers"] } diff --git a/apps/docs/content/docs/orm/core-concepts/supported-databases/mysql.mdx b/apps/docs/content/docs/orm/core-concepts/supported-databases/mysql.mdx index 66da6a59d6..bf24514d8d 100644 --- a/apps/docs/content/docs/orm/core-concepts/supported-databases/mysql.mdx +++ b/apps/docs/content/docs/orm/core-concepts/supported-databases/mysql.mdx @@ -97,12 +97,12 @@ Standard MySQL (5.6+) or MariaDB (10.0+) servers. **Connection string arguments:** -| Argument | Default | Description | -|----------|---------|-------------| -| `connect_timeout` | `5` | Seconds to wait for connection | -| `sslcert` | | Path to server certificate | -| `sslidentity` | | Path to PKCS12 certificate | -| `sslaccept` | `accept_invalid_certs` | Certificate validation mode | +| Argument | Default | Description | +| ----------------- | ---------------------- | ------------------------------ | +| `connect_timeout` | `5` | Seconds to wait for connection | +| `sslcert` | | Path to server certificate | +| `sslidentity` | | Path to PKCS12 certificate | +| `sslaccept` | `accept_invalid_certs` | Certificate validation mode | ### PlanetScale @@ -114,12 +114,14 @@ Serverless MySQL-compatible database built on Vitess clustering system. - Non-blocking schema changes **Key features:** + - Enterprise scalability across multiple servers - Database branches for schema testing - Non-blocking schema deployments - Serverless-optimized (avoids connection limits) **Branch workflow:** + 1. **Development branches** - Test schema changes freely 2. **Production branches** - Protected, require deploy requests 3. **Deploy requests** - Merge dev changes to production @@ -175,17 +177,17 @@ model Comment { ### Type mapping between MySQL and Prisma schema -| Prisma | MySQL/MariaDB | -|--------|---------------| -| `String` | `VARCHAR(191)` | -| `Boolean` | `TINYINT(1)` | -| `Int` | `INT` | -| `BigInt` | `BIGINT` | -| `Float` | `DOUBLE` | -| `Decimal` | `DECIMAL(65,30)` | -| `DateTime` | `DATETIME(3)` | -| `Json` | `JSON` | -| `Bytes` | `LONGBLOB` | +| Prisma | MySQL/MariaDB | +| ---------- | ---------------- | +| `String` | `VARCHAR(191)` | +| `Boolean` | `TINYINT(1)` | +| `Int` | `INT` | +| `BigInt` | `BIGINT` | +| `Float` | `DOUBLE` | +| `Decimal` | `DECIMAL(65,30)` | +| `DateTime` | `DATETIME(3)` | +| `Json` | `JSON` | +| `Bytes` | `LONGBLOB` | See [full type mapping reference](/orm/reference/prisma-schema-reference#model-field-scalar-types) for complete details. @@ -223,6 +225,7 @@ model User { **Connection troubleshooting:** PlanetScale production branches are read-only for direct DDL. If you get error P3022, ensure you're: + - Using `prisma db push` instead of `prisma migrate` - Working on a development branch, or - Using a deploy request to update production diff --git a/apps/docs/content/docs/orm/core-concepts/supported-databases/postgresql.mdx b/apps/docs/content/docs/orm/core-concepts/supported-databases/postgresql.mdx index 27cc79eb53..9d08c4a97e 100644 --- a/apps/docs/content/docs/orm/core-concepts/supported-databases/postgresql.mdx +++ b/apps/docs/content/docs/orm/core-concepts/supported-databases/postgresql.mdx @@ -1,6 +1,6 @@ --- title: PostgreSQL -description: 'Use Prisma ORM with PostgreSQL databases including self-hosted, serverless (Neon, Supabase), and CockroachDB' +description: "Use Prisma ORM with PostgreSQL databases including self-hosted, serverless (Neon, Supabase), and CockroachDB" url: /orm/core-concepts/supported-databases/postgresql metaTitle: PostgreSQL database connector metaDescription: This page explains how Prisma can connect to a PostgreSQL database using the PostgreSQL database connector. @@ -96,13 +96,13 @@ Standard PostgreSQL server (9.6+). **Connection string arguments:** -| Argument | Default | Description | -|----------|---------|-------------| -| `schema` | `public` | PostgreSQL schema to use | -| `connect_timeout` | `5` | Seconds to wait for connection (0 = no timeout) | -| `sslmode` | `prefer` | TLS mode: `prefer`, `disable`, `require` | -| `sslcert` | | Path to server certificate | -| `sslidentity` | | Path to PKCS12 certificate | +| Argument | Default | Description | +| ----------------- | -------- | ----------------------------------------------- | +| `schema` | `public` | PostgreSQL schema to use | +| `connect_timeout` | `5` | Seconds to wait for connection (0 = no timeout) | +| `sslmode` | `prefer` | TLS mode: `prefer`, `disable`, `require` | +| `sslcert` | | Path to server certificate | +| `sslidentity` | | Path to PKCS12 certificate | ### Neon @@ -123,11 +123,13 @@ Serverless PostgreSQL with automatic scaling and branching. PostgreSQL hosting with built-in auth, storage, and real-time features. **Connection types:** + - **Direct:** `db.[project-ref].supabase.co:5432` - **Transaction pooler:** Port `6543` with `?pgbouncer=true` - **Session pooler:** Port `5432` on pooler host **Key features:** + - Supavisor connection pooling - Built-in PostgreSQL extensions - Integrated with Supabase ecosystem @@ -146,11 +148,11 @@ Distributed, PostgreSQL-compatible database designed for scalability and high av **Key differences:** -| Feature | PostgreSQL | CockroachDB | -|---------|-----------|-------------| -| Native types | `VARCHAR(n)` | `STRING(n)` | -| ID generation | `autoincrement()` | Uses `unique_rowid()` | -| Sequential IDs | Recommended | Avoid (use `autoincrement()` instead) | +| Feature | PostgreSQL | CockroachDB | +| -------------- | ----------------- | ------------------------------------- | +| Native types | `VARCHAR(n)` | `STRING(n)` | +| ID generation | `autoincrement()` | Uses `unique_rowid()` | +| Sequential IDs | Recommended | Avoid (use `autoincrement()` instead) | **ID generation example:** @@ -176,17 +178,17 @@ model User { ### Prisma to PostgreSQL -| Prisma | PostgreSQL | CockroachDB | -|--------|-----------|-------------| -| `String` | `text` | `STRING` | -| `Boolean` | `boolean` | `BOOL` | -| `Int` | `integer` | `INT4` | -| `BigInt` | `bigint` | `INT8` | -| `Float` | `double precision` | `FLOAT8` | -| `Decimal` | `decimal(65,30)` | `DECIMAL` | -| `DateTime` | `timestamp(3)` | `TIMESTAMP` | -| `Json` | `jsonb` | `JSONB` | -| `Bytes` | `bytea` | `BYTES` | +| Prisma | PostgreSQL | CockroachDB | +| ---------- | ------------------ | ----------- | +| `String` | `text` | `STRING` | +| `Boolean` | `boolean` | `BOOL` | +| `Int` | `integer` | `INT4` | +| `BigInt` | `bigint` | `INT8` | +| `Float` | `double precision` | `FLOAT8` | +| `Decimal` | `decimal(65,30)` | `DECIMAL` | +| `DateTime` | `timestamp(3)` | `TIMESTAMP` | +| `Json` | `jsonb` | `JSONB` | +| `Bytes` | `bytea` | `BYTES` | See [full type mapping reference](/orm/reference/prisma-schema-reference#model-field-scalar-types) for complete details. @@ -213,13 +215,14 @@ DATABASE_URL="postgresql://user:pass@localhost/db?host=/var/run/postgresql/" ```ts const adapter = new PrismaPg( { connectionString: process.env.DATABASE_URL }, - { schema: "mySchema" } + { schema: "mySchema" }, ); ``` **Connection pool defaults (Prisma ORM v7):** Driver adapters use `pg` defaults which differ from v6: + - **Connection timeout:** `0` (no timeout) vs v6's `5s` - **Idle timeout:** `10s` vs v6's `300s` diff --git a/apps/docs/content/docs/orm/core-concepts/supported-databases/sql-server.mdx b/apps/docs/content/docs/orm/core-concepts/supported-databases/sql-server.mdx index c9c3c29d2a..d40a5406e3 100644 --- a/apps/docs/content/docs/orm/core-concepts/supported-databases/sql-server.mdx +++ b/apps/docs/content/docs/orm/core-concepts/supported-databases/sql-server.mdx @@ -77,24 +77,25 @@ sqlserver://host:1433;user={MyServer/User};password={Pass:Word;};database=db ### Connection string arguments -| Argument | Default | Description | -|----------|---------|-------------| -| `database` / `initial catalog` | `master` | Database name | -| `user` / `username` / `uid` | | SQL Server login or Windows username (if using `integratedSecurity`) | -| `password` / `pwd` | | Password for user | -| `encrypt` | `true` | Use TLS: `true` (always), `false` (login only) | -| `integratedSecurity` | | Windows authentication: `true`, `false`, `yes`, `no` | -| `schema` | `dbo` | Schema prefix for all queries | -| `connectTimeout` | `5` | Seconds to wait for connection | -| `socketTimeout` | | Seconds to wait for each query | -| `poolTimeout` | `10` | Seconds to wait for connection from pool | -| `trustServerCertificate` | `false` | Trust server certificate without validation | -| `trustServerCertificateCA` | | Path to CA certificate file (`.pem`, `.crt`, `.der`) | -| `ApplicationName` | | Application name for the connection | +| Argument | Default | Description | +| ------------------------------ | -------- | -------------------------------------------------------------------- | +| `database` / `initial catalog` | `master` | Database name | +| `user` / `username` / `uid` | | SQL Server login or Windows username (if using `integratedSecurity`) | +| `password` / `pwd` | | Password for user | +| `encrypt` | `true` | Use TLS: `true` (always), `false` (login only) | +| `integratedSecurity` | | Windows authentication: `true`, `false`, `yes`, `no` | +| `schema` | `dbo` | Schema prefix for all queries | +| `connectTimeout` | `5` | Seconds to wait for connection | +| `socketTimeout` | | Seconds to wait for each query | +| `poolTimeout` | `10` | Seconds to wait for connection from pool | +| `trustServerCertificate` | `false` | Trust server certificate without validation | +| `trustServerCertificateCA` | | Path to CA certificate file (`.pem`, `.crt`, `.der`) | +| `ApplicationName` | | Application name for the connection | :::warning[Prisma ORM v7: Connection pool defaults changed] Driver adapters use `mssql` driver defaults which differ from v6: + - **Connection timeout:** `15s` (vs v6's `5s`) - **Idle timeout:** `30s` (vs v6's `300s`) @@ -124,17 +125,17 @@ sqlserver://mycomputer\sql2019;database=sample;integratedSecurity=true;trustServ ## Type mappings -| Prisma | SQL Server | -|--------|------------| -| `String` | `NVARCHAR(1000)` | -| `Boolean` | `BIT` | -| `Int` | `INT` | -| `BigInt` | `BIGINT` | -| `Float` | `FLOAT(53)` | -| `Decimal` | `DECIMAL(32,16)` | -| `DateTime` | `DATETIME2` | -| `Json` | Not supported | -| `Bytes` | `VARBINARY(MAX)` | +| Prisma | SQL Server | +| ---------- | ---------------- | +| `String` | `NVARCHAR(1000)` | +| `Boolean` | `BIT` | +| `Int` | `INT` | +| `BigInt` | `BIGINT` | +| `Float` | `FLOAT(53)` | +| `Decimal` | `DECIMAL(32,16)` | +| `DateTime` | `DATETIME2` | +| `Json` | Not supported | +| `Bytes` | `VARBINARY(MAX)` | See [full type mapping reference](/orm/reference/prisma-schema-reference#model-field-scalar-types) for complete details. @@ -173,6 +174,7 @@ sqlserver://host:1433;database=db;schema=dbo;... **Destructive changes:** Some operations require table recreation: + - Adding/removing `autoincrement()` - Dropping all columns from a table diff --git a/apps/docs/content/docs/orm/core-concepts/supported-databases/sqlite.mdx b/apps/docs/content/docs/orm/core-concepts/supported-databases/sqlite.mdx index 216ab34b2e..af10244658 100644 --- a/apps/docs/content/docs/orm/core-concepts/supported-databases/sqlite.mdx +++ b/apps/docs/content/docs/orm/core-concepts/supported-databases/sqlite.mdx @@ -1,6 +1,6 @@ --- title: SQLite -description: 'Use Prisma ORM with SQLite databases including local SQLite, Turso (libSQL), and Cloudflare D1' +description: "Use Prisma ORM with SQLite databases including local SQLite, Turso (libSQL), and Cloudflare D1" url: /orm/core-concepts/supported-databases/sqlite metaTitle: SQLite database connector metaDescription: This page explains how Prisma can connect to a SQLite database using the SQLite database connector. @@ -100,6 +100,7 @@ Edge-hosted, distributed SQLite-compatible database. - Use local SQLite file + Turso CLI for migrations (see [Turso docs](https://docs.turso.tech/)) **Key differences:** + - Remote access over HTTP - Replication and automated backups - Schema changes via `prisma migrate diff` + Turso CLI @@ -113,6 +114,7 @@ Serverless SQLite database for Cloudflare Workers. - Local (`.wrangler/state`) and remote versions available **Key differences:** + - No transaction support currently - Migrations via Wrangler: `wrangler d1 migrations apply` - Deploy with Cloudflare Workers @@ -147,7 +149,7 @@ Configure how `DateTime` values are stored: ```ts const adapter = new PrismaBetterSqlite3( { url: "file:./dev.db" }, - { timestampFormat: "unixepoch-ms" } // For backward compatibility + { timestampFormat: "unixepoch-ms" }, // For backward compatibility ); ``` diff --git a/apps/docs/content/docs/orm/index.mdx b/apps/docs/content/docs/orm/index.mdx index 9ec833cb03..8e69ed5f94 100644 --- a/apps/docs/content/docs/orm/index.mdx +++ b/apps/docs/content/docs/orm/index.mdx @@ -1,6 +1,6 @@ --- title: Prisma ORM -description: 'Prisma ORM is a next-generation Node.js and TypeScript ORM that provides type-safe database access, migrations, and a visual data editor.' +description: "Prisma ORM is a next-generation Node.js and TypeScript ORM that provides type-safe database access, migrations, and a visual data editor." url: /orm metaTitle: What is Prisma ORM? (Overview) metaDescription: This page gives a high-level overview of what Prisma ORM is and how it works. It's a great starting point for Prisma newcomers @@ -29,12 +29,14 @@ Prisma takes a different approach: ## When to use Prisma **Prisma is a good fit if you:** + - Build server-side applications (REST, GraphQL, gRPC, serverless) - Value type safety and developer experience - Work in a team and want a clear, declarative schema - Need migrations, querying, and data modeling in one toolkit **Consider alternatives if you:** + - Need full control over every SQL query (use raw SQL drivers) - Want a no-code backend (use a BaaS like Supabase or Firebase) - Need an auto-generated CRUD GraphQL API (use Hasura or PostGraphile) diff --git a/apps/docs/content/docs/orm/more/best-practices.mdx b/apps/docs/content/docs/orm/more/best-practices.mdx index f2ad4a0b9f..c2e70809c6 100644 --- a/apps/docs/content/docs/orm/more/best-practices.mdx +++ b/apps/docs/content/docs/orm/more/best-practices.mdx @@ -1,8 +1,8 @@ --- title: Best practices -description: 'Learn production-ready patterns for schema design, query optimization, type safety, security, and deployment with Prisma ORM.' +description: "Learn production-ready patterns for schema design, query optimization, type safety, security, and deployment with Prisma ORM." metaTitle: Best practices -metaDescription: 'Learn production-ready patterns for schema design, query optimization, type safety, security, and deployment with Prisma ORM.' +metaDescription: "Learn production-ready patterns for schema design, query optimization, type safety, security, and deployment with Prisma ORM." url: /orm/more/best-practices --- @@ -52,6 +52,7 @@ model Comment { @@index([postId]) } ``` + ::: ### Index strategy @@ -112,14 +113,14 @@ The `schema.prisma` file (containing the `generator` block) and `migrations/` di Create one global `PrismaClient` instance and reuse it throughout your application. Creating multiple instances creates multiple connection pools, which can exhaust your database's connection limit and slow down queries. ```ts title="lib/prisma.ts" -import { PrismaClient } from '../generated/prisma/client' -import { PrismaPg } from '@prisma/adapter-pg' +import { PrismaClient } from "../generated/prisma/client"; +import { PrismaPg } from "@prisma/adapter-pg"; const adapter = new PrismaPg({ - connectionString: process.env.DATABASE_URL -}) + connectionString: process.env.DATABASE_URL, +}); -export const prisma = new PrismaClient({ adapter }) +export const prisma = new PrismaClient({ adapter }); ``` **Serverless environments:** @@ -133,23 +134,23 @@ The N+1 problem occurs when you run 1 query to fetch a list, then 1 additional q ```ts title="n-plus-one.ts" // ❌ Bad: N+1 queries (1 + N queries) -const users = await prisma.user.findMany() +const users = await prisma.user.findMany(); for (const user of users) { const posts = await prisma.post.findMany({ - where: { authorId: user.id } - }) + where: { authorId: user.id }, + }); } // ✅ Good: Single query with include const users = await prisma.user.findMany({ - include: { posts: true } -}) + include: { posts: true }, +}); // ✅ Good: Batch with IN filter -const users = await prisma.user.findMany() +const users = await prisma.user.findMany(); const posts = await prisma.post.findMany({ - where: { authorId: { in: users.map(u => u.id) } } -}) + where: { authorId: { in: users.map((u) => u.id) } }, +}); ``` ### Select only needed fields @@ -161,27 +162,27 @@ const user = await prisma.user.findFirst({ select: { id: true, email: true, - role: true - } -}) + role: true, + }, +}); ``` Use `omit` to blacklist fields you want excluded (useful for sensitive data): ```ts title="omit.ts" -import { PrismaClient } from '../generated/prisma/client' -import { PrismaPg } from '@prisma/adapter-pg' +import { PrismaClient } from "../generated/prisma/client"; +import { PrismaPg } from "@prisma/adapter-pg"; const adapter = new PrismaPg({ - connectionString: process.env.DATABASE_URL -}) + connectionString: process.env.DATABASE_URL, +}); const prisma = new PrismaClient({ adapter, omit: { - user: { secretValue: true } - } -}) + user: { secretValue: true }, + }, +}); ``` You cannot combine `select` and `omit` in the same query. @@ -194,8 +195,8 @@ Use **offset pagination** for small datasets where jumping to arbitrary pages is const posts = await prisma.post.findMany({ skip: 40, take: 10, - where: { email: { contains: 'prisma.io' } }, -}) + where: { email: { contains: "prisma.io" } }, +}); ``` Use **cursor-based pagination** for large datasets or infinite scroll. Cursor-based pagination scales better because it uses indexed columns to find the starting position instead of traversing skipped rows: @@ -208,9 +209,9 @@ const posts = await prisma.post.findMany({ id: lastPost.id, }, orderBy: { - id: 'asc', + id: "asc", }, -}) +}); ``` ### Batch operations @@ -219,16 +220,13 @@ Use bulk methods when operating on multiple records: ```ts title="batch-operations.ts" await prisma.user.createMany({ - data: [ - { email: 'alice@prisma.io' }, - { email: 'bob@prisma.io' } - ] -}) + data: [{ email: "alice@prisma.io" }, { email: "bob@prisma.io" }], +}); await prisma.post.updateMany({ where: { published: false }, - data: { published: true } -}) + data: { published: true }, +}); ``` Bulk operations (`createMany`, `createManyAndReturn`, `updateMany`, `updateManyAndReturn`, and `deleteMany`) [automatically run as transactions](/orm/prisma-client/queries/transactions#batch-operations), so all writes either succeed together or are rolled back if something fails. @@ -238,10 +236,10 @@ Bulk operations (`createMany`, `createManyAndReturn`, `updateMany`, `updateManyA Prefer Prisma ORM's query API. Use raw SQL only when you need features not supported by Prisma ORM or heavily optimized queries: ```ts title="raw-query.ts" -const email = 'user@example.com' +const email = "user@example.com"; const users = await prisma.$queryRaw` SELECT * FROM "User" WHERE email = ${email} -` +`; ``` :::warning @@ -255,14 +253,14 @@ Never concatenate user input into SQL strings. Always use parameterized queries Use Prisma ORM's generated types instead of duplicating interfaces: ```ts title="generated-types.ts" -import type { User } from '../generated/prisma/client' +import type { User } from "../generated/prisma/client"; async function getAdminEmails(): Promise { const admins: User[] = await prisma.user.findMany({ - where: { role: 'ADMIN' } - }) + where: { role: "ADMIN" }, + }); - return admins.map(a => a.email) + return admins.map((a) => a.email); } ``` @@ -271,16 +269,16 @@ async function getAdminEmails(): Promise { Always validate and sanitize user input before database operations: ```ts title="input-validation.ts" -import { z } from 'zod' +import { z } from "zod"; const createUserSchema = z.object({ email: z.string().email(), - name: z.string().min(1).max(100) -}) + name: z.string().min(1).max(100), +}); async function createUser(input: unknown) { - const data = createUserSchema.parse(input) - return prisma.user.create({ data }) + const data = createUserSchema.parse(input); + return prisma.user.create({ data }); } ``` @@ -294,17 +292,14 @@ Prisma ORM's API is safe by default. For raw queries, always use parameterized q // ✅ Safe: tagged template const result = await prisma.$queryRaw` SELECT * FROM "User" WHERE email = ${email} -` +`; // ✅ Safe: parameterized -const result = await prisma.$queryRawUnsafe( - 'SELECT * FROM "User" WHERE email = $1', - email -) +const result = await prisma.$queryRawUnsafe('SELECT * FROM "User" WHERE email = $1', email); // ❌ Unsafe: string concatenation -const query = `SELECT * FROM "User" WHERE email = '${email}'` -const result = await prisma.$queryRawUnsafe(query) +const query = `SELECT * FROM "User" WHERE email = '${email}'`; +const result = await prisma.$queryRawUnsafe(query); ``` ### Sensitive data handling @@ -312,26 +307,26 @@ const result = await prisma.$queryRawUnsafe(query) Exclude sensitive fields from query results: ```ts title="sensitive-data.ts" -import { PrismaClient } from '../generated/prisma/client' -import { PrismaPg } from '@prisma/adapter-pg' +import { PrismaClient } from "../generated/prisma/client"; +import { PrismaPg } from "@prisma/adapter-pg"; const adapter = new PrismaPg({ - connectionString: process.env.DATABASE_URL -}) + connectionString: process.env.DATABASE_URL, +}); // Global exclusion const prisma = new PrismaClient({ adapter, omit: { - user: { secretValue: true } - } -}) + user: { secretValue: true }, + }, +}); // Per-query exclusion const user = await prisma.user.findUnique({ where: { id: 1 }, - omit: { secretValue: true } -}) + omit: { secretValue: true }, +}); ``` ## Testing @@ -351,24 +346,24 @@ Use a dedicated test database that can be reset freely: Mock Prisma ORM using `jest-mock-extended`: ```ts title="unit-test.ts" -import { PrismaClient } from '../generated/prisma/client' -import { mockDeep } from 'jest-mock-extended' +import { PrismaClient } from "../generated/prisma/client"; +import { mockDeep } from "jest-mock-extended"; -const prismaMock = mockDeep() +const prismaMock = mockDeep(); -test('finds user by email', async () => { +test("finds user by email", async () => { prismaMock.user.findUnique.mockResolvedValue({ id: 1, - email: 'test@example.com', - name: 'Test User' - }) + email: "test@example.com", + name: "Test User", + }); const user = await prismaMock.user.findUnique({ - where: { email: 'test@example.com' } - }) + where: { email: "test@example.com" }, + }); - expect(user).toBeDefined() -}) + expect(user).toBeDefined(); +}); ``` ### Integration tests with real database @@ -376,32 +371,32 @@ test('finds user by email', async () => { Use a real database with Prisma Migrate: ```ts title="integration-test.ts" -import { PrismaClient } from '../generated/prisma/client' -import { PrismaPg } from '@prisma/adapter-pg' +import { PrismaClient } from "../generated/prisma/client"; +import { PrismaPg } from "@prisma/adapter-pg"; const adapter = new PrismaPg({ - connectionString: process.env.DATABASE_URL -}) + connectionString: process.env.DATABASE_URL, +}); -const prisma = new PrismaClient({ adapter }) +const prisma = new PrismaClient({ adapter }); beforeEach(async () => { await prisma.user.create({ - data: { email: 'test@example.com', name: 'Test' } - }) -}) + data: { email: "test@example.com", name: "Test" }, + }); +}); afterEach(async () => { - await prisma.user.deleteMany() -}) + await prisma.user.deleteMany(); +}); -test('creates user', async () => { +test("creates user", async () => { const user = await prisma.user.create({ - data: { email: 'new@example.com', name: 'New User' } - }) + data: { email: "new@example.com", name: "New User" }, + }); - expect(user.email).toBe('new@example.com') -}) + expect(user.email).toBe("new@example.com"); +}); ``` ## Production deployment @@ -438,21 +433,21 @@ For AWS Lambda, Vercel, Cloudflare Workers, or similar platforms: 3. Consider external connection poolers (like PgBouncer) for high-concurrency workloads ```ts title="serverless-handler.ts" -import { PrismaClient } from '../generated/prisma/client' -import { PrismaPg } from '@prisma/adapter-pg' +import { PrismaClient } from "../generated/prisma/client"; +import { PrismaPg } from "@prisma/adapter-pg"; const adapter = new PrismaPg({ - connectionString: process.env.DATABASE_URL -}) + connectionString: process.env.DATABASE_URL, +}); -const prisma = new PrismaClient({ adapter }) +const prisma = new PrismaClient({ adapter }); export async function handler(event) { - const users = await prisma.user.findMany() + const users = await prisma.user.findMany(); return { statusCode: 200, - body: JSON.stringify(users) - } + body: JSON.stringify(users), + }; } ``` diff --git a/apps/docs/content/docs/orm/more/comparisons/prisma-and-drizzle.mdx b/apps/docs/content/docs/orm/more/comparisons/prisma-and-drizzle.mdx index e68a20c950..d7acbf1630 100644 --- a/apps/docs/content/docs/orm/more/comparisons/prisma-and-drizzle.mdx +++ b/apps/docs/content/docs/orm/more/comparisons/prisma-and-drizzle.mdx @@ -285,7 +285,6 @@ const posts = await db.select().from(posts).where(ilike(posts.title, "%Hello Wor Both Drizzle and Prisma ORM have the ability to log queries and the underlying SQL generated. - These products work hand-in-hand with Prisma ORM to offer comprehensive data tooling, making building data-driven applications easy by following [Data DX](https://www.datadx.io/) principles. ## Safer Changes and Fewer Bugs diff --git a/apps/docs/content/docs/orm/more/comparisons/prisma-and-mongoose.mdx b/apps/docs/content/docs/orm/more/comparisons/prisma-and-mongoose.mdx index 88beabe37d..1cf24beb7c 100644 --- a/apps/docs/content/docs/orm/more/comparisons/prisma-and-mongoose.mdx +++ b/apps/docs/content/docs/orm/more/comparisons/prisma-and-mongoose.mdx @@ -59,7 +59,7 @@ const userWithPost = await prisma.user.findUnique({ include: { post: true, }, -}) +}); ``` ```ts tab="Fluent API" @@ -69,7 +69,7 @@ const userWithPost = await prisma.user id: 2, }, }) - .post() + .post(); ``` **Mongoose** @@ -186,17 +186,17 @@ const user = await prisma.user.create({ ```ts tab="Using create" const user = await User.create({ - name: 'Alice', - email: 'alice@prisma.io', -}) + name: "Alice", + email: "alice@prisma.io", +}); ``` ```ts tab="Using save" const user = new User({ - name: 'Alice', - email: 'alice@prisma.io', -}) -await user.save() + name: "Alice", + email: "alice@prisma.io", +}); +await user.save(); ``` ## Updating objects @@ -221,15 +221,15 @@ const updatedUser = await User.findOneAndUpdate( { _id: 2 }, { $set: { - name: 'Alicia', + name: "Alicia", }, - } -) + }, +); ``` ```ts tab="Using save" -user.name = 'Alicia' -await user.save() +user.name = "Alicia"; +await user.save(); ``` ## Deleting objects diff --git a/apps/docs/content/docs/orm/more/comparisons/prisma-and-sequelize.mdx b/apps/docs/content/docs/orm/more/comparisons/prisma-and-sequelize.mdx index d8aef4c764..9801b352f2 100644 --- a/apps/docs/content/docs/orm/more/comparisons/prisma-and-sequelize.mdx +++ b/apps/docs/content/docs/orm/more/comparisons/prisma-and-sequelize.mdx @@ -239,17 +239,17 @@ const user = await prisma.user.create({ ```ts tab="Using save" const user = User.build({ - name: 'Alice', - email: 'alice@prisma,io', -}) -await user.save() + name: "Alice", + email: "alice@prisma,io", +}); +await user.save(); ``` ```ts tab="Using create" const user = await User.create({ - name: 'Alice', - email: 'alice@prisma,io', -}) + name: "Alice", + email: "alice@prisma,io", +}); ``` ### Updating objects @@ -270,16 +270,16 @@ const user = await prisma.user.update({ **Sequelize** ```ts tab="Using save" -user.name = 'James' -user.email = ' alice@prisma.com' -await user.save() +user.name = "James"; +user.email = " alice@prisma.com"; +await user.save(); ``` ```ts tab="Using update" await User.update({ - name: 'James', - email: 'james@prisma.io', -}) + name: "James", + email: "james@prisma.io", +}); ``` ### Deleting objects @@ -378,49 +378,49 @@ const user = await prisma.user.create({ return sequelize.$transaction(async (t) => { const user = await User.create( { - name: 'Alice', - email: 'alice@prisma,io', + name: "Alice", + email: "alice@prisma,io", }, { transaction: t, - } - ) + }, + ); const post1 = await Post.create( { - title: 'Join us for GraphQL Conf in 2019', + title: "Join us for GraphQL Conf in 2019", }, { transaction: t, - } - ) + }, + ); const post2 = await Post.create( { - title: 'Subscribe to GraphQL Weekly for GraphQL news', + title: "Subscribe to GraphQL Weekly for GraphQL news", }, { transaction: t, - } - ) - await user.setPosts([post1, post2]) -}) + }, + ); + await user.setPosts([post1, post2]); +}); ``` ```ts tab="Automatic" return sequelize.$transaction(async (transaction) => { try { const user = await User.create({ - name: 'Alice', - email: 'alice@prisma,io', - }) + name: "Alice", + email: "alice@prisma,io", + }); const post1 = await Post.create({ - title: 'Join us for GraphQL Conf in 2019', - }) + title: "Join us for GraphQL Conf in 2019", + }); const post2 = await Post.create({ - title: 'Subscribe to GraphQL Weekly for GraphQL news', - }) - await user.setPosts([post1, post2]) + title: "Subscribe to GraphQL Weekly for GraphQL news", + }); + await user.setPosts([post1, post2]); } catch (e) { - return transaction.rollback() + return transaction.rollback(); } -}) +}); ``` diff --git a/apps/docs/content/docs/orm/more/dev-environment/meta.json b/apps/docs/content/docs/orm/more/dev-environment/meta.json index 4ee3e32ddb..0f90c30caf 100644 --- a/apps/docs/content/docs/orm/more/dev-environment/meta.json +++ b/apps/docs/content/docs/orm/more/dev-environment/meta.json @@ -1,7 +1,4 @@ { "title": "Dev environment", - "pages": [ - "environment-variables", - "editor-setup" - ] + "pages": ["environment-variables", "editor-setup"] } diff --git a/apps/docs/content/docs/orm/more/releases.mdx b/apps/docs/content/docs/orm/more/releases.mdx index ff0b6c5fdc..6e79894b5a 100644 --- a/apps/docs/content/docs/orm/more/releases.mdx +++ b/apps/docs/content/docs/orm/more/releases.mdx @@ -1,9 +1,9 @@ --- title: ORM releases and maturity levels -description: 'Learn about the release process, versioning, and maturity of Prisma ORM components and how to deal with breaking changes that might happen throughout releases' +description: "Learn about the release process, versioning, and maturity of Prisma ORM components and how to deal with breaking changes that might happen throughout releases" url: /orm/more/releases metaTitle: ORM releases and maturity levels -metaDescription: 'Learn about the release process, versioning, and maturity of Prisma ORM components and how to deal with breaking changes that might happen throughout releases.' +metaDescription: "Learn about the release process, versioning, and maturity of Prisma ORM components and how to deal with breaking changes that might happen throughout releases." --- This page explains the release process of Prisma ORM, how it's versioned and how to deal with breaking changes that might happen throughout releases. diff --git a/apps/docs/content/docs/orm/more/troubleshooting/many-to-many-relations.mdx b/apps/docs/content/docs/orm/more/troubleshooting/many-to-many-relations.mdx index 53925162aa..6caac4d1a7 100644 --- a/apps/docs/content/docs/orm/more/troubleshooting/many-to-many-relations.mdx +++ b/apps/docs/content/docs/orm/more/troubleshooting/many-to-many-relations.mdx @@ -1,6 +1,6 @@ --- title: Many-to-many relations -description: 'Learn how to model, query, and convert many-to-many relations with Prisma ORM' +description: "Learn how to model, query, and convert many-to-many relations with Prisma ORM" url: /orm/more/troubleshooting/many-to-many-relations metaTitle: Modeling and querying many-to-many relations metaDescription: Learn how you can model and query implicit and explicit many-to-many relations with Prisma ORM diff --git a/apps/docs/content/docs/orm/more/troubleshooting/nextjs.mdx b/apps/docs/content/docs/orm/more/troubleshooting/nextjs.mdx index ca3d99fe20..fa1b611853 100644 --- a/apps/docs/content/docs/orm/more/troubleshooting/nextjs.mdx +++ b/apps/docs/content/docs/orm/more/troubleshooting/nextjs.mdx @@ -2,7 +2,7 @@ title: Next.js description: Best practices and troubleshooting for using Prisma ORM with Next.js applications url: /orm/more/troubleshooting/nextjs -metaDescription: 'Learn best practices, monorepo strategies, and dynamic usage techniques for Prisma ORM in Next.js applications.' +metaDescription: "Learn best practices, monorepo strategies, and dynamic usage techniques for Prisma ORM in Next.js applications." metaTitle: Comprehensive Guide to Using Prisma ORM with Next.js --- diff --git a/apps/docs/content/docs/orm/more/troubleshooting/nuxt.mdx b/apps/docs/content/docs/orm/more/troubleshooting/nuxt.mdx index 574b67e105..a561ea3884 100644 --- a/apps/docs/content/docs/orm/more/troubleshooting/nuxt.mdx +++ b/apps/docs/content/docs/orm/more/troubleshooting/nuxt.mdx @@ -3,7 +3,7 @@ title: Nuxt description: Learn how to integrate Prisma ORM with your Nuxt application url: /orm/more/troubleshooting/nuxt metaTitle: Add Prisma ORM Easily to Your Nuxt Apps -metaDescription: 'Learn how to easily add Prisma ORM to your Nuxt apps, use its features, and understand its limitations.' +metaDescription: "Learn how to easily add Prisma ORM to your Nuxt apps, use its features, and understand its limitations." --- :::warning diff --git a/apps/docs/content/docs/orm/more/troubleshooting/raw-sql-comparisons.mdx b/apps/docs/content/docs/orm/more/troubleshooting/raw-sql-comparisons.mdx index 9753e0a6c5..5b13cdbc28 100644 --- a/apps/docs/content/docs/orm/more/troubleshooting/raw-sql-comparisons.mdx +++ b/apps/docs/content/docs/orm/more/troubleshooting/raw-sql-comparisons.mdx @@ -50,8 +50,7 @@ const response = ### SQLite ```js -const response = - await prisma.$queryRaw`SELECT * FROM "Post" WHERE "likesCount" < "commentsCount";`; +const response = await prisma.$queryRaw`SELECT * FROM "Post" WHERE "likesCount" < "commentsCount";`; ``` ## Comparing date values @@ -87,6 +86,5 @@ const response = ### SQLite ```js -const response = - await prisma.$queryRaw`SELECT * FROM "Project" WHERE "completedDate" > "dueDate";`; +const response = await prisma.$queryRaw`SELECT * FROM "Project" WHERE "completedDate" > "dueDate";`; ``` diff --git a/apps/docs/content/docs/orm/prisma-client/client-extensions/client.mdx b/apps/docs/content/docs/orm/prisma-client/client-extensions/client.mdx index d1eedbf0f1..b126648a7d 100644 --- a/apps/docs/content/docs/orm/prisma-client/client-extensions/client.mdx +++ b/apps/docs/content/docs/orm/prisma-client/client-extensions/client.mdx @@ -1,9 +1,9 @@ --- title: Add methods to Prisma Client -description: 'Extend the functionality of Prisma Client, client component' +description: "Extend the functionality of Prisma Client, client component" url: /orm/prisma-client/client-extensions/client -metaTitle: 'Prisma Client extensions: client component' -metaDescription: 'Extend the functionality of Prisma Client, client component' +metaTitle: "Prisma Client extensions: client component" +metaDescription: "Extend the functionality of Prisma Client, client component" --- You can use the `client` [Prisma Client extensions](/orm/prisma-client/client-extensions) component to add top-level methods to Prisma Client. @@ -27,7 +27,6 @@ The following example uses the `client` component to add two methods to Prisma C - `$log` outputs a message. - `$totalQueries` returns the number of queries executed by the current client instance. - ```ts let total = 0; const prisma = new PrismaClient().$extends({ diff --git a/apps/docs/content/docs/orm/prisma-client/client-extensions/extension-examples.mdx b/apps/docs/content/docs/orm/prisma-client/client-extensions/extension-examples.mdx index 0902e5a751..26209b1cf7 100644 --- a/apps/docs/content/docs/orm/prisma-client/client-extensions/extension-examples.mdx +++ b/apps/docs/content/docs/orm/prisma-client/client-extensions/extension-examples.mdx @@ -10,9 +10,9 @@ metaDescription: Explore the Prisma Client extensions that have been built by Pr The following is a list of extensions we've built at Prisma: -| Extension | Description | -| :------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------- | -| [`@prisma/extension-read-replicas`](https://github.com/prisma/extension-read-replicas) | Adds read replica support to Prisma Client | +| Extension | Description | +| :------------------------------------------------------------------------------------- | :----------------------------------------- | +| [`@prisma/extension-read-replicas`](https://github.com/prisma/extension-read-replicas) | Adds read replica support to Prisma Client | ## Extensions made by Prisma's community diff --git a/apps/docs/content/docs/orm/prisma-client/client-extensions/model.mdx b/apps/docs/content/docs/orm/prisma-client/client-extensions/model.mdx index b34a381ef2..474da05fa5 100644 --- a/apps/docs/content/docs/orm/prisma-client/client-extensions/model.mdx +++ b/apps/docs/content/docs/orm/prisma-client/client-extensions/model.mdx @@ -1,9 +1,9 @@ --- title: Add custom methods to your models -description: 'Extend the functionality of Prisma Client, model component' +description: "Extend the functionality of Prisma Client, model component" url: /orm/prisma-client/client-extensions/model -metaTitle: 'Prisma Client extensions: model component' -metaDescription: 'Extend the functionality of Prisma Client, model component' +metaTitle: "Prisma Client extensions: model component" +metaDescription: "Extend the functionality of Prisma Client, model component" --- You can use the `model` [Prisma Client extensions](/orm/prisma-client/client-extensions) component type to add custom methods to your models. diff --git a/apps/docs/content/docs/orm/prisma-client/client-extensions/query.mdx b/apps/docs/content/docs/orm/prisma-client/client-extensions/query.mdx index 83a70cee3f..d2b23e5f65 100644 --- a/apps/docs/content/docs/orm/prisma-client/client-extensions/query.mdx +++ b/apps/docs/content/docs/orm/prisma-client/client-extensions/query.mdx @@ -1,9 +1,9 @@ --- title: Create custom Prisma Client queries -description: 'Extend the functionality of Prisma Client, query component' +description: "Extend the functionality of Prisma Client, query component" url: /orm/prisma-client/client-extensions/query -metaTitle: 'Prisma Client extensions: query component' -metaDescription: 'Extend the functionality of Prisma Client, query component' +metaTitle: "Prisma Client extensions: query component" +metaDescription: "Extend the functionality of Prisma Client, query component" --- You can use the `query` [Prisma Client extensions](/orm/prisma-client/client-extensions) component type to hook into the query life-cycle and modify an incoming query or its result. diff --git a/apps/docs/content/docs/orm/prisma-client/client-extensions/result.mdx b/apps/docs/content/docs/orm/prisma-client/client-extensions/result.mdx index 9a436244cd..b41bc4abfb 100644 --- a/apps/docs/content/docs/orm/prisma-client/client-extensions/result.mdx +++ b/apps/docs/content/docs/orm/prisma-client/client-extensions/result.mdx @@ -1,9 +1,9 @@ --- title: Add custom fields and methods to query results -description: 'Extend the functionality of Prisma Client, result component' +description: "Extend the functionality of Prisma Client, result component" url: /orm/prisma-client/client-extensions/result -metaTitle: 'Prisma Client extensions: result component' -metaDescription: 'Extend the functionality of Prisma Client, result component' +metaTitle: "Prisma Client extensions: result component" +metaDescription: "Extend the functionality of Prisma Client, result component" --- You can use the `result` [Prisma Client extensions](/orm/prisma-client/client-extensions) component type to add custom fields and methods to query results. diff --git a/apps/docs/content/docs/orm/prisma-client/client-extensions/shared-extensions/index.mdx b/apps/docs/content/docs/orm/prisma-client/client-extensions/shared-extensions/index.mdx index 390a65e0f2..09be4d3994 100644 --- a/apps/docs/content/docs/orm/prisma-client/client-extensions/shared-extensions/index.mdx +++ b/apps/docs/content/docs/orm/prisma-client/client-extensions/shared-extensions/index.mdx @@ -2,9 +2,8 @@ title: Shared Prisma Client extensions description: Share extensions or import shared extensions into your Prisma project url: /orm/prisma-client/client-extensions/shared-extensions -metaTitle: 'Shared Prisma Client extensions' -metaDescription: 'Share extensions or import shared extensions into your Prisma project' - +metaTitle: "Shared Prisma Client extensions" +metaDescription: "Share extensions or import shared extensions into your Prisma project" --- You can share your [Prisma Client extensions](/orm/prisma-client/client-extensions) with other users, either as packages or as modules, and import extensions that other users create into your project. diff --git a/apps/docs/content/docs/orm/prisma-client/client-extensions/shared-extensions/permit-rbac.mdx b/apps/docs/content/docs/orm/prisma-client/client-extensions/shared-extensions/permit-rbac.mdx index ee956c4042..3e4cf8c83b 100644 --- a/apps/docs/content/docs/orm/prisma-client/client-extensions/shared-extensions/permit-rbac.mdx +++ b/apps/docs/content/docs/orm/prisma-client/client-extensions/shared-extensions/permit-rbac.mdx @@ -1,8 +1,8 @@ --- title: Fine-Grained Authorization (Permit) -description: 'Learn how to implement RBAC, ABAC, and ReBAC authorization in your Prisma applications' +description: "Learn how to implement RBAC, ABAC, and ReBAC authorization in your Prisma applications" url: /orm/prisma-client/client-extensions/shared-extensions/permit-rbac -metaDescription: 'Learn how to implement RBAC, ABAC, and ReBAC authorization in your Prisma applications' +metaDescription: "Learn how to implement RBAC, ABAC, and ReBAC authorization in your Prisma applications" metaTitle: Fine-Grained Authorization (Permit) --- diff --git a/apps/docs/content/docs/orm/prisma-client/client-extensions/type-utilities.mdx b/apps/docs/content/docs/orm/prisma-client/client-extensions/type-utilities.mdx index 65fa343c4b..7037fb913a 100644 --- a/apps/docs/content/docs/orm/prisma-client/client-extensions/type-utilities.mdx +++ b/apps/docs/content/docs/orm/prisma-client/client-extensions/type-utilities.mdx @@ -1,9 +1,9 @@ --- title: Type utilities -description: 'Advanced type safety: improve type safety in your custom model methods' +description: "Advanced type safety: improve type safety in your custom model methods" url: /orm/prisma-client/client-extensions/type-utilities -metaTitle: 'Prisma Client Extensions: Type utilities' -metaDescription: 'Advanced type safety: improve type safety in your custom model methods' +metaTitle: "Prisma Client Extensions: Type utilities" +metaDescription: "Advanced type safety: improve type safety in your custom model methods" --- Several type utilities exist within Prisma Client that can assist in the creation of highly type-safe extensions. diff --git a/apps/docs/content/docs/orm/prisma-client/debugging-and-troubleshooting/meta.json b/apps/docs/content/docs/orm/prisma-client/debugging-and-troubleshooting/meta.json index 332becc96a..557bcde6f1 100644 --- a/apps/docs/content/docs/orm/prisma-client/debugging-and-troubleshooting/meta.json +++ b/apps/docs/content/docs/orm/prisma-client/debugging-and-troubleshooting/meta.json @@ -1,7 +1,4 @@ { "title": "Debugging and Troubleshooting", - "pages": [ - "debugging", - "handling-exceptions-and-errors" - ] + "pages": ["debugging", "handling-exceptions-and-errors"] } diff --git a/apps/docs/content/docs/orm/prisma-client/deployment/caveats-when-deploying-to-aws-platforms.mdx b/apps/docs/content/docs/orm/prisma-client/deployment/caveats-when-deploying-to-aws-platforms.mdx index c0c798c10e..f8805b0063 100644 --- a/apps/docs/content/docs/orm/prisma-client/deployment/caveats-when-deploying-to-aws-platforms.mdx +++ b/apps/docs/content/docs/orm/prisma-client/deployment/caveats-when-deploying-to-aws-platforms.mdx @@ -64,4 +64,3 @@ The `sslmode=no-verify` setting passes `rejectUnauthorized: false` to the SSL co ### Note While using `sslmode=no-verify` can be a quick fix, it bypasses SSL verification and might not meet security requirements for production environments. In such cases, ensure that a valid SSL certificate is properly configured. - diff --git a/apps/docs/content/docs/orm/prisma-client/deployment/deploy-migrations-from-a-local-environment.mdx b/apps/docs/content/docs/orm/prisma-client/deployment/deploy-migrations-from-a-local-environment.mdx index 2b2c9bc08b..dab1903853 100644 --- a/apps/docs/content/docs/orm/prisma-client/deployment/deploy-migrations-from-a-local-environment.mdx +++ b/apps/docs/content/docs/orm/prisma-client/deployment/deploy-migrations-from-a-local-environment.mdx @@ -28,7 +28,7 @@ DATABASE_URL="postgresql://johndoe:randompassword@prod-db.example.com:5432/my_pr 3. Run `prisma migrate deploy` -**⛔ We strongly discourage this solution due to the following reasons** +**⛔ We strongly discourage this solution due to the following reasons** - You risk exposing your production database connection URL to version control. - You may accidentally use your production connection URL instead and in turn **override or delete your production database**. diff --git a/apps/docs/content/docs/orm/prisma-client/deployment/edge/deploy-to-cloudflare.mdx b/apps/docs/content/docs/orm/prisma-client/deployment/edge/deploy-to-cloudflare.mdx index d70d53abb3..ae3d36ebd7 100644 --- a/apps/docs/content/docs/orm/prisma-client/deployment/edge/deploy-to-cloudflare.mdx +++ b/apps/docs/content/docs/orm/prisma-client/deployment/edge/deploy-to-cloudflare.mdx @@ -41,8 +41,6 @@ This command: - Creates a `prisma` directory containing a `schema.prisma` file for your database models. - Creates a `.env` file with your `DATABASE_URL`. - - ### Using an edge-compatible driver When deploying a Cloudflare Worker that uses Prisma ORM, you need to use an [edge-compatible driver](/orm/prisma-client/deployment/edge/overview#edge-compatibility-of-database-drivers) and its respective [driver adapter](/orm/core-concepts/supported-databases/database-drivers#driver-adapters) for Prisma ORM. @@ -59,7 +57,6 @@ There's [also work being done](https://github.com/sidorares/node-mysql2/pull/228 If your application uses PostgreSQL, we recommend using [Prisma Postgres](/postgres). It is fully supported on edge runtimes and does not require a specialized edge-compatible driver. Review the [Prisma Postgres serverless driver limitations](/postgres/database/serverless-driver#limitations) to understand current constraints. - ### Setting your database connection URL as an environment variable First, ensure that your `datasource` block in your Prisma schema is configured correctly. Database connection URLs are configured in `prisma.config.ts`: diff --git a/apps/docs/content/docs/orm/prisma-client/deployment/edge/deploy-to-deno-deploy.mdx b/apps/docs/content/docs/orm/prisma-client/deployment/edge/deploy-to-deno-deploy.mdx index 40780e616b..937dcbd323 100644 --- a/apps/docs/content/docs/orm/prisma-client/deployment/edge/deploy-to-deno-deploy.mdx +++ b/apps/docs/content/docs/orm/prisma-client/deployment/edge/deploy-to-deno-deploy.mdx @@ -29,6 +29,7 @@ deno run -A npm:prisma@latest init --db Enter a name for your project and choose a database region. This command: + - Connects to the [Prisma Data Platform](https://console.prisma.io) (opens browser for authentication) - Creates a `prisma/schema.prisma` file for your database models - Creates a `.env` file with your `DATABASE_URL` @@ -105,6 +106,7 @@ deno task db:push ``` This command: + 1. Creates the `Task` table in your Prisma Postgres database 2. Generates the Prisma Client with full type safety @@ -216,14 +218,14 @@ Deno.serve({ port: 8000 }, handler); This creates a full CRUD API with the following endpoints: -| Method | Endpoint | Description | -|--------|----------|-------------| -| GET | `/` | API info | -| GET | `/tasks` | List all tasks | -| POST | `/tasks` | Create a new task | -| GET | `/tasks/:id` | Get a specific task | -| PATCH | `/tasks/:id` | Update a task | -| DELETE | `/tasks/:id` | Delete a task | +| Method | Endpoint | Description | +| ------ | ------------ | ------------------- | +| GET | `/` | API info | +| GET | `/tasks` | List all tasks | +| POST | `/tasks` | Create a new task | +| GET | `/tasks/:id` | Get a specific task | +| PATCH | `/tasks/:id` | Update a task | +| DELETE | `/tasks/:id` | Delete a task | ## 6. Test your application locally diff --git a/apps/docs/content/docs/orm/prisma-client/deployment/edge/meta.json b/apps/docs/content/docs/orm/prisma-client/deployment/edge/meta.json index 33337c96f7..b2ad5529e1 100644 --- a/apps/docs/content/docs/orm/prisma-client/deployment/edge/meta.json +++ b/apps/docs/content/docs/orm/prisma-client/deployment/edge/meta.json @@ -1,9 +1,4 @@ { "title": "Edge", - "pages": [ - "overview", - "deploy-to-vercel", - "deploy-to-cloudflare", - "deploy-to-deno-deploy" - ] + "pages": ["overview", "deploy-to-vercel", "deploy-to-cloudflare", "deploy-to-deno-deploy"] } diff --git a/apps/docs/content/docs/orm/prisma-client/deployment/edge/overview.mdx b/apps/docs/content/docs/orm/prisma-client/deployment/edge/overview.mdx index 3fc7f34a5c..aa7fff24db 100644 --- a/apps/docs/content/docs/orm/prisma-client/deployment/edge/overview.mdx +++ b/apps/docs/content/docs/orm/prisma-client/deployment/edge/overview.mdx @@ -2,7 +2,7 @@ title: Deploying edge functions with Prisma ORM description: Learn how to deploy your Prisma-backed apps to edge functions like Cloudflare Workers or Vercel Edge Functions url: /orm/prisma-client/deployment/edge/overview -metaTitle: 'Overview: Deploy Prisma ORM at the Edge' +metaTitle: "Overview: Deploy Prisma ORM at the Edge" metaDescription: Learn how to deploy your Prisma-backed apps to edge functions like Cloudflare Workers or Vercel Edge Functions --- @@ -29,7 +29,6 @@ Here is a brief overview of all the edge function providers that are currently s Deploying edge functions that use Prisma ORM on Cloudflare and Vercel is currently in [Preview](/orm/more/releases#preview). - ## Edge-compatibility of database drivers ### Why are there limitations around database drivers in edge functions? @@ -40,7 +39,7 @@ In particular, the constraint of not being able to freely open TCP connections m :::note -We recommend using [Prisma Postgres](/postgres). It is fully supported on edge runtimes and does not require a specialized edge-compatible driver. +We recommend using [Prisma Postgres](/postgres). It is fully supported on edge runtimes and does not require a specialized edge-compatible driver. ::: diff --git a/apps/docs/content/docs/orm/prisma-client/deployment/serverless/deploy-to-aws-lambda.mdx b/apps/docs/content/docs/orm/prisma-client/deployment/serverless/deploy-to-aws-lambda.mdx index 0108dbaf85..e9b40b0eff 100644 --- a/apps/docs/content/docs/orm/prisma-client/deployment/serverless/deploy-to-aws-lambda.mdx +++ b/apps/docs/content/docs/orm/prisma-client/deployment/serverless/deploy-to-aws-lambda.mdx @@ -1,9 +1,9 @@ --- title: Deploy to AWS Lambda -description: 'Learn how to deploy your Prisma ORM-backed applications to AWS Lambda with AWS SAM, Serverless Framework, or SST' +description: "Learn how to deploy your Prisma ORM-backed applications to AWS Lambda with AWS SAM, Serverless Framework, or SST" url: /orm/prisma-client/deployment/serverless/deploy-to-aws-lambda metaTitle: Deploy your application using Prisma ORM to AWS Lambda -metaDescription: 'Learn how to deploy your Prisma ORM-backed applications to AWS Lambda with AWS SAM, Serverless Framework, or SST' +metaDescription: "Learn how to deploy your Prisma ORM-backed applications to AWS Lambda with AWS SAM, Serverless Framework, or SST" --- :::info[Quick summary] @@ -81,7 +81,6 @@ Packaging deployment-example-sls for stage dev (us-east-1) . ``` - ## Deploying with SST ### Working with environment variables @@ -98,9 +97,7 @@ const globalForPrisma = global as unknown as { prisma: PrismaClient }; const adapter = new PrismaPg({ connectionString }); -export const prisma = - globalForPrisma.prisma || - new PrismaClient({ adapter }); +export const prisma = globalForPrisma.prisma || new PrismaClient({ adapter }); if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma; diff --git a/apps/docs/content/docs/orm/prisma-client/observability-and-logging/meta.json b/apps/docs/content/docs/orm/prisma-client/observability-and-logging/meta.json index 05e8e2dbf5..a9af54f521 100644 --- a/apps/docs/content/docs/orm/prisma-client/observability-and-logging/meta.json +++ b/apps/docs/content/docs/orm/prisma-client/observability-and-logging/meta.json @@ -1,4 +1,4 @@ { "title": "Observability and Logging", - "pages": [ "logging", "opentelemetry-tracing", "sql-comments"] + "pages": ["logging", "opentelemetry-tracing", "sql-comments"] } diff --git a/apps/docs/content/docs/orm/prisma-client/observability-and-logging/sql-comments.mdx b/apps/docs/content/docs/orm/prisma-client/observability-and-logging/sql-comments.mdx index 2e704bab16..9757322dd3 100644 --- a/apps/docs/content/docs/orm/prisma-client/observability-and-logging/sql-comments.mdx +++ b/apps/docs/content/docs/orm/prisma-client/observability-and-logging/sql-comments.mdx @@ -1,9 +1,9 @@ --- title: SQL comments -description: 'Add metadata to your SQL queries as comments for improved observability, debugging, and tracing' +description: "Add metadata to your SQL queries as comments for improved observability, debugging, and tracing" url: /orm/prisma-client/observability-and-logging/sql-comments metaTitle: SQL comments -metaDescription: 'Add metadata to your SQL queries as comments for improved observability, debugging, and tracing.' +metaDescription: "Add metadata to your SQL queries as comments for improved observability, debugging, and tracing." --- SQL comments allow you to append metadata to your database queries, making it easier to correlate queries with application context. Prisma ORM supports the [sqlcommenter format](https://google.github.io/sqlcommenter/) developed by Google, which is widely supported by database monitoring tools. diff --git a/apps/docs/content/docs/orm/prisma-client/queries/advanced/query-optimization-performance.mdx b/apps/docs/content/docs/orm/prisma-client/queries/advanced/query-optimization-performance.mdx index 7eed33f1eb..14a5b727cc 100644 --- a/apps/docs/content/docs/orm/prisma-client/queries/advanced/query-optimization-performance.mdx +++ b/apps/docs/content/docs/orm/prisma-client/queries/advanced/query-optimization-performance.mdx @@ -46,6 +46,7 @@ see Prisma ORM queries in Query Insights. Docs: https://www.prisma.io/docs/query ## Debugging performance issues Common causes of slow queries: + - Over-fetching data - Missing indexes - Not caching repeated queries @@ -114,9 +115,7 @@ Prisma's dataloader automatically batches `findUnique()` queries in the same tic ```ts // Instead of findMany per user, use: -return context.prisma.user - .findUnique({ where: { id: parent.id } }) - .posts(); +return context.prisma.user.findUnique({ where: { id: parent.id } }).posts(); ``` ### Using JOINs with `relationLoadStrategy` @@ -348,13 +347,13 @@ const usersWithPosts = await prisma.user.findMany({ // GOOD: 2 queries with in filter const users = await prisma.user.findMany({}); const posts = await prisma.post.findMany({ - where: { authorId: { in: users.map(u => u.id) } }, + where: { authorId: { in: users.map((u) => u.id) } }, }); // BEST: 1 query with join const posts = await prisma.post.findMany({ relationLoadStrategy: "join", - where: { authorId: { in: users.map(u => u.id) } }, + where: { authorId: { in: users.map((u) => u.id) } }, }); ``` diff --git a/apps/docs/content/docs/orm/prisma-client/queries/aggregation-grouping-summarizing.mdx b/apps/docs/content/docs/orm/prisma-client/queries/aggregation-grouping-summarizing.mdx index b8b2ff0cc1..24609549c2 100644 --- a/apps/docs/content/docs/orm/prisma-client/queries/aggregation-grouping-summarizing.mdx +++ b/apps/docs/content/docs/orm/prisma-client/queries/aggregation-grouping-summarizing.mdx @@ -1,9 +1,9 @@ --- -title: 'Aggregation, grouping, and summarizing' -description: 'Use Prisma Client to aggregate, group by, count, and select distinct.' +title: "Aggregation, grouping, and summarizing" +description: "Use Prisma Client to aggregate, group by, count, and select distinct." url: /orm/prisma-client/queries/aggregation-grouping-summarizing -metaTitle: 'Aggregation, grouping, and summarizing (Concepts)' -metaDescription: 'Use Prisma Client to aggregate, group by, count, and select distinct.' +metaTitle: "Aggregation, grouping, and summarizing (Concepts)" +metaDescription: "Use Prisma Client to aggregate, group by, count, and select distinct." --- Prisma Client allows you to count records, aggregate number fields, and select distinct field values. @@ -17,7 +17,7 @@ const aggregations = await prisma.user.aggregate({ _avg: { age: true }, }); -console.log('Average age:' + aggregations._avg.age); +console.log("Average age:" + aggregations._avg.age); ``` You can combine aggregation with filtering and ordering. For example, the following query returns the average age of users: @@ -31,14 +31,14 @@ const aggregations = await prisma.user.aggregate({ _avg: { age: true }, where: { email: { - contains: 'prisma.io', + contains: "prisma.io", }, }, - orderBy: { age: 'asc' }, + orderBy: { age: "asc" }, take: 10, }); -console.log('Average age:' + aggregations._avg.age); +console.log("Average age:" + aggregations._avg.age); ``` ### Aggregate values are nullable @@ -76,7 +76,7 @@ The following example groups all users by the `country` field and returns the to ```ts const groupUsers = await prisma.user.groupBy({ - by: ['country'], + by: ["country"], _sum: { profileViews: true }, }); ``` @@ -92,7 +92,7 @@ If you have a single element in the `by` option, you can use the following short ```ts const groupUsers = await prisma.user.groupBy({ - by: 'country', + by: "country", }); ``` @@ -106,12 +106,12 @@ Use `where` to filter all records **before grouping**. The following example gro ```ts const groupUsers = await prisma.user.groupBy({ - by: ['country'], + by: ["country"], where: { // [!code highlight] email: { // [!code highlight] - contains: 'prisma.io', // [!code highlight] + contains: "prisma.io", // [!code highlight] }, // [!code highlight] }, // [!code highlight] _sum: { @@ -126,13 +126,13 @@ Use `having` to filter **entire groups** by an aggregate value such as the sum o ```ts const groupUsers = await prisma.user.groupBy({ - by: ['country'], + by: ["country"], where: { email: { - contains: 'prisma.io', + contains: "prisma.io", }, }, - _sum: { profileViews: true, }, + _sum: { profileViews: true }, having: { // [!code highlight] profileViews: { @@ -154,11 +154,11 @@ For example, the following query groups all users that are _not_ from Sweden or ```ts const fd = await prisma.user.groupBy({ - by: ['country'], + by: ["country"], where: { country: { // [!code highlight] - notIn: ['Sweden', 'Ghana'], // [!code highlight] + notIn: ["Sweden", "Ghana"], // [!code highlight] }, // [!code highlight] }, _sum: { @@ -178,11 +178,11 @@ The following query technically achieves the same result, but excludes users fro ```ts const groupUsers = await prisma.user.groupBy({ - by: ['country'], + by: ["country"], where: { country: { // [!code highlight] - not: 'Sweden', // [!code highlight] + not: "Sweden", // [!code highlight] }, // [!code highlight] }, _sum: { @@ -191,7 +191,7 @@ const groupUsers = await prisma.user.groupBy({ having: { country: { // [!code highlight] - not: 'Ghana', // [!code highlight] + not: "Ghana", // [!code highlight] }, // [!code highlight] profileViews: { _min: { @@ -218,13 +218,13 @@ You can **order by aggregate group**. The following example sorts each `city` gr ```ts const groupBy = await prisma.user.groupBy({ - by: ['city'], + by: ["city"], _count: { city: true, }, orderBy: { _count: { - city: 'desc', + city: "desc", }, }, }); @@ -244,12 +244,12 @@ The following query orders groups by country, skips the first two groups, and re ```ts const groupBy = await prisma.user.groupBy({ - by: ['country'], + by: ["country"], _sum: { profileViews: true, }, orderBy: { - country: 'desc', + country: "desc", }, skip: 2, take: 2, @@ -391,7 +391,7 @@ await prisma.user.findMany({ select: { _count: { select: { - posts: { where: { title: 'Hello!' } }, + posts: { where: { title: "Hello!" } }, }, }, }, @@ -408,7 +408,7 @@ await prisma.user.findMany({ _count: { select: { posts: { - where: { comments: { some: { author: { is: { name: 'Alice' } } } } }, + where: { comments: { some: { author: { is: { name: "Alice" } } } } }, }, }, }, @@ -469,7 +469,7 @@ The following example returns all fields for all `User` records with distinct `n ```ts const result = await prisma.user.findMany({ where: {}, - distinct: ['name'], + distinct: ["name"], }); ``` @@ -477,7 +477,7 @@ The following example returns distinct `role` field values (for example, `ADMIN` ```ts const distinctRoles = await prisma.user.findMany({ - distinct: ['role'], + distinct: ["role"], select: { role: true, }, @@ -537,9 +537,9 @@ model Play { ```ts const distinctScores = await prisma.play.findMany({ - distinct: ['playerId', 'gameId'], + distinct: ["playerId", "gameId"], orderBy: { - score: 'desc', + score: "desc", }, select: { score: true, diff --git a/apps/docs/content/docs/orm/prisma-client/queries/relation-queries.mdx b/apps/docs/content/docs/orm/prisma-client/queries/relation-queries.mdx index 0f7d82a9f2..3d7b3753f1 100644 --- a/apps/docs/content/docs/orm/prisma-client/queries/relation-queries.mdx +++ b/apps/docs/content/docs/orm/prisma-client/queries/relation-queries.mdx @@ -1,9 +1,9 @@ --- title: Relation queries -description: 'Prisma Client provides convenient queries for working with relations, such as a fluent API, nested writes (transactions), nested reads and relation filters' +description: "Prisma Client provides convenient queries for working with relations, such as a fluent API, nested writes (transactions), nested reads and relation filters" url: /orm/prisma-client/queries/relation-queries metaTitle: Relation queries (Concepts) -metaDescription: 'Prisma Client provides convenient queries for working with relations, such as a fluent API, nested writes (transactions), nested reads and relation filters.' +metaDescription: "Prisma Client provides convenient queries for working with relations, such as a fluent API, nested writes (transactions), nested reads and relation filters." --- A key feature of Prisma Client is the ability to query [relations](/orm/prisma-schema/data-model/relations) between two or more models. Relation queries include: @@ -90,30 +90,30 @@ const user = await prisma.user.findFirst({ ```json { - id: 19, - name: null, - email: 'emma@prisma.io', - profileViews: 0, - role: 'USER', - coinflips: [], - posts: [ + "id": 19, + "name": null, + "email": "emma@prisma.io", + "profileViews": 0, + "role": "USER", + "coinflips": [], + "posts": [ { - id: 20, - title: 'My first post', - published: true, - authorId: 19, - comments: null, - views: 0, - likes: 0 + "id": 20, + "title": "My first post", + "published": true, + "authorId": 19, + "comments": null, + "views": 0, + "likes": 0 }, { - id: 21, - title: 'How to make cookies', - published: true, - authorId: 19, - comments: null, - views: 0, - likes: 0 + "id": 21, + "title": "How to make cookies", + "published": true, + "authorId": 19, + "comments": null, + "views": 0, + "likes": 0 } ] } @@ -133,21 +133,21 @@ const post = await prisma.post.findFirst({ ```json { - id: 17, - title: 'How to make cookies', - published: true, - authorId: 16, - comments: null, - views: 0, - likes: 0, - author: { - id: 16, - name: null, - email: 'orla@prisma.io', - profileViews: 0, - role: 'USER', - coinflips: [], - }, + "id": 17, + "title": "How to make cookies", + "published": true, + "authorId": 16, + "comments": null, + "views": 0, + "likes": 0, + "author": { + "id": 16, + "name": null, + "email": "orla@prisma.io", + "profileViews": 0, + "role": "USER", + "coinflips": [] + } } ``` @@ -169,42 +169,42 @@ const user = await prisma.user.findFirst({ ```json { - "id": 40, - "name": "Yvette", - "email": "yvette@prisma.io", - "profileViews": 0, - "role": "USER", - "coinflips": [], - "testing": [], - "city": null, - "country": "Sweden", - "posts": [ - { - "id": 66, - "title": "How to make an omelette", - "published": true, - "authorId": 40, - "comments": null, - "views": 0, - "likes": 0, - "categories": [ - { - "id": 3, - "name": "Easy cooking" - } - ] - }, + "id": 40, + "name": "Yvette", + "email": "yvette@prisma.io", + "profileViews": 0, + "role": "USER", + "coinflips": [], + "testing": [], + "city": null, + "country": "Sweden", + "posts": [ + { + "id": 66, + "title": "How to make an omelette", + "published": true, + "authorId": 40, + "comments": null, + "views": 0, + "likes": 0, + "categories": [ { - "id": 67, - "title": "How to eat an omelette", - "published": true, - "authorId": 40, - "comments": null, - "views": 0, - "likes": 0, - "categories": [] + "id": 3, + "name": "Easy cooking" } - ] + ] + }, + { + "id": 67, + "title": "How to eat an omelette", + "published": true, + "authorId": 40, + "comments": null, + "views": 0, + "likes": 0, + "categories": [] + } + ] } ``` @@ -227,8 +227,8 @@ const user = await prisma.user.findFirst({ ```json { - name: "Elsa", - posts: [ { title: 'My first post' }, { title: 'How to make cookies' } ] + "name": "Elsa", + "posts": [{ "title": "My first post" }, { "title": "How to make cookies" }] } ``` @@ -254,10 +254,7 @@ const user = await prisma.user.findFirst({ "profileViews": 0, "role": "USER", "coinflips": [], - "posts": [ - { "title": "How to grow salad" }, - { "title": "How to ride a horse" } - ] + "posts": [{ "title": "How to grow salad" }, { "title": "How to ride a horse" }] } ``` @@ -415,30 +412,30 @@ const result = await prisma.user.create({ ```json { - id: 29, - name: 'Elsa', - email: 'elsa@prisma.io', - profileViews: 0, - role: 'USER', - coinflips: [], - posts: [ + "id": 29, + "name": "Elsa", + "email": "elsa@prisma.io", + "profileViews": 0, + "role": "USER", + "coinflips": [], + "posts": [ { - id: 22, - title: 'How to make an omelette', - published: true, - authorId: 29, - comments: null, - views: 0, - likes: 0 + "id": 22, + "title": "How to make an omelette", + "published": true, + "authorId": 29, + "comments": null, + "views": 0, + "likes": 0 }, { - id: 23, - title: 'How to eat an omelette', - published: true, - authorId: 29, - comments: null, - views: 0, - likes: 0 + "id": 23, + "title": "How to eat an omelette", + "published": true, + "authorId": 29, + "comments": null, + "views": 0, + "likes": 0 } ] } @@ -507,42 +504,42 @@ const result = await prisma.user.create({ ```json { - "id": 40, - "name": "Yvette", - "email": "yvette@prisma.io", - "profileViews": 0, - "role": "USER", - "coinflips": [], - "testing": [], - "city": null, - "country": "Sweden", - "posts": [ - { - "id": 66, - "title": "How to make an omelette", - "published": true, - "authorId": 40, - "comments": null, - "views": 0, - "likes": 0, - "categories": [ - { - "id": 3, - "name": "Easy cooking" - } - ] - }, + "id": 40, + "name": "Yvette", + "email": "yvette@prisma.io", + "profileViews": 0, + "role": "USER", + "coinflips": [], + "testing": [], + "city": null, + "country": "Sweden", + "posts": [ + { + "id": 66, + "title": "How to make an omelette", + "published": true, + "authorId": 40, + "comments": null, + "views": 0, + "likes": 0, + "categories": [ { - "id": 67, - "title": "How to eat an omelette", - "published": true, - "authorId": 40, - "comments": null, - "views": 0, - "likes": 0, - "categories": [] + "id": 3, + "name": "Easy cooking" } - ] + ] + }, + { + "id": 67, + "title": "How to eat an omelette", + "published": true, + "authorId": 40, + "comments": null, + "views": 0, + "likes": 0, + "categories": [] + } + ] } ``` @@ -579,35 +576,35 @@ const result = await prisma.user.create({ ```json { - "id": 43, - "name": null, - "email": "saanvi@prisma.io", - "profileViews": 0, - "role": "USER", - "coinflips": [], - "testing": [], - "city": null, - "country": "India", - "posts": [ - { - "id": 70, - "title": "My first post", - "published": true, - "authorId": 43, - "comments": null, - "views": 0, - "likes": 0 - }, - { - "id": 71, - "title": "My second post", - "published": true, - "authorId": 43, - "comments": null, - "views": 0, - "likes": 0 - } - ] + "id": 43, + "name": null, + "email": "saanvi@prisma.io", + "profileViews": 0, + "role": "USER", + "coinflips": [], + "testing": [], + "city": null, + "country": "India", + "posts": [ + { + "id": 70, + "title": "My first post", + "published": true, + "authorId": 43, + "comments": null, + "views": 0, + "likes": 0 + }, + { + "id": 71, + "title": "My second post", + "published": true, + "authorId": 43, + "comments": null, + "views": 0, + "likes": 0 + } + ] } ``` @@ -691,21 +688,21 @@ const result = await prisma.user.create({ ```json { - id: 27, - name: null, - email: 'vlad@prisma.io', - profileViews: 0, - role: 'USER', - coinflips: [], - posts: [ + "id": 27, + "name": null, + "email": "vlad@prisma.io", + "profileViews": 0, + "role": "USER", + "coinflips": [], + "posts": [ { - id: 10, - title: 'An existing post', - published: true, - authorId: 27, - comments: {}, - views: 0, - likes: 0 + "id": 10, + "title": "An existing post", + "published": true, + "authorId": 27, + "comments": {}, + "views": 0, + "likes": 0 } ] } @@ -774,19 +771,19 @@ const result = await prisma.post.create({ ```json { - id: 26, - title: 'How to make croissants', - published: true, - authorId: 43, - views: 0, - likes: 0, - author: { - id: 43, - name: 'Viola', - email: 'viola@prisma.io', - profileViews: 0, - role: 'USER', - coinflips: [] + "id": 26, + "title": "How to make croissants", + "published": true, + "authorId": 43, + "views": 0, + "likes": 0, + "author": { + "id": 43, + "name": "Viola", + "email": "viola@prisma.io", + "profileViews": 0, + "role": "USER", + "coinflips": [] } } ``` @@ -814,13 +811,13 @@ const result = await prisma.user.update({ ```json { - id: 16, - name: null, - email: 'orla@prisma.io', - profileViews: 0, - role: 'USER', - coinflips: [], - posts: [] + "id": 16, + "name": null, + "email": "orla@prisma.io", + "profileViews": 0, + "role": "USER", + "coinflips": [], + "posts": [] } ``` @@ -845,14 +842,14 @@ const result = await prisma.post.update({ ```json { - id: 23, - title: 'How to eat an omelette', - published: true, - authorId: null, - comments: null, - views: 0, - likes: 0, - author: null + "id": 23, + "title": "How to eat an omelette", + "published": true, + "authorId": null, + "comments": null, + "views": 0, + "likes": 0, + "author": null } ``` @@ -879,13 +876,13 @@ const result = await prisma.user.update({ ```json { - id: 16, - name: null, - email: 'orla@prisma.io', - profileViews: 0, - role: 'USER', - coinflips: [], - posts: [] + "id": 16, + "name": null, + "email": "orla@prisma.io", + "profileViews": 0, + "role": "USER", + "coinflips": [], + "posts": [] } ``` @@ -1226,7 +1223,6 @@ const postsByUser = await prisma.post.findMany({ The main difference between the queries is that the fluent API call is translated into two separate database queries while the other one only generates a single query (see this [GitHub issue](https://github.com/prisma/prisma/issues/1984)) - This request returns all categories by a specific post: ```ts diff --git a/apps/docs/content/docs/orm/prisma-client/setup-and-configuration/databases-connections/connection-management.mdx b/apps/docs/content/docs/orm/prisma-client/setup-and-configuration/databases-connections/connection-management.mdx index 8ace30491d..d2475e0a42 100644 --- a/apps/docs/content/docs/orm/prisma-client/setup-and-configuration/databases-connections/connection-management.mdx +++ b/apps/docs/content/docs/orm/prisma-client/setup-and-configuration/databases-connections/connection-management.mdx @@ -84,7 +84,7 @@ async function main() { main() .then(async () => { - await prisma.$disconnect(); //[!code highlight] + await prisma.$disconnect(); //[!code highlight] }) .catch(async (e) => { console.error(e); diff --git a/apps/docs/content/docs/orm/prisma-client/setup-and-configuration/databases-connections/index.mdx b/apps/docs/content/docs/orm/prisma-client/setup-and-configuration/databases-connections/index.mdx index 3ae42d7cc3..8319e125b5 100644 --- a/apps/docs/content/docs/orm/prisma-client/setup-and-configuration/databases-connections/index.mdx +++ b/apps/docs/content/docs/orm/prisma-client/setup-and-configuration/databases-connections/index.mdx @@ -95,8 +95,8 @@ main(); In local tests with Postgres, MySQL, and SQLite, each Prisma CLI command typically uses a single connection. The table below shows the ranges observed in these tests. Your environment _may_ produce slightly different results. -| Command | Connections | Description | -| ------------------------------------------------------------------------------ | ----------- | ------------------------------------------------ | +| Command | Connections | Description | +| ---------------------------------------------------------------------- | ----------- | ------------------------------------------------ | | [`migrate status`](/orm/reference/prisma-cli-reference#migrate-status) | 1 | Checks the status of migrations | | [`migrate dev`](/orm/reference/prisma-cli-reference#migrate-dev) | 1–4 | Applies pending migrations in development | | [`migrate diff`](/orm/reference/prisma-cli-reference#migrate-diff) | 1–2 | Compares database schema with migration history | diff --git a/apps/docs/content/docs/orm/prisma-client/setup-and-configuration/databases-connections/pgbouncer.mdx b/apps/docs/content/docs/orm/prisma-client/setup-and-configuration/databases-connections/pgbouncer.mdx index b55f87ff6c..75948ab286 100644 --- a/apps/docs/content/docs/orm/prisma-client/setup-and-configuration/databases-connections/pgbouncer.mdx +++ b/apps/docs/content/docs/orm/prisma-client/setup-and-configuration/databases-connections/pgbouncer.mdx @@ -1,9 +1,9 @@ --- title: Configure Prisma Client with PgBouncer -description: 'Configure Prisma Client with PgBouncer and other poolers: when to use pgbouncer=true, required transaction mode, prepared statements, and Prisma Migrate workarounds' +description: "Configure Prisma Client with PgBouncer and other poolers: when to use pgbouncer=true, required transaction mode, prepared statements, and Prisma Migrate workarounds" url: /orm/prisma-client/setup-and-configuration/databases-connections/pgbouncer metaTitle: Configure Prisma Client with PgBouncer -metaDescription: 'Configure Prisma Client with PgBouncer and other poolers: when to use pgbouncer=true, required transaction mode, prepared statements, and Prisma Migrate workarounds.' +metaDescription: "Configure Prisma Client with PgBouncer and other poolers: when to use pgbouncer=true, required transaction mode, prepared statements, and Prisma Migrate workarounds." --- An external connection pooler like PgBouncer holds a connection pool to the database, and proxies incoming client connections by sitting between Prisma Client and the database. This reduces the number of processes a database has to handle at any given time. diff --git a/apps/docs/content/docs/orm/prisma-client/setup-and-configuration/introduction.mdx b/apps/docs/content/docs/orm/prisma-client/setup-and-configuration/introduction.mdx index dde5fcc9de..9b6cfc3c38 100644 --- a/apps/docs/content/docs/orm/prisma-client/setup-and-configuration/introduction.mdx +++ b/apps/docs/content/docs/orm/prisma-client/setup-and-configuration/introduction.mdx @@ -15,13 +15,13 @@ Prisma Client is an auto-generated and type-safe query builder that's _tailored_ In order to set up Prisma Client, you need a Prisma Config and a [Prisma schema file](/orm/prisma-schema/overview): ```ts title="prisma.config.ts" tab="Prisma Config" -import 'dotenv/config'; -import { defineConfig, env } from 'prisma/config'; +import "dotenv/config"; +import { defineConfig, env } from "prisma/config"; export default defineConfig({ - schema: './prisma/schema.prisma', + schema: "./prisma/schema.prisma", datasource: { - url: env('DATABASE_URL'), + url: env("DATABASE_URL"), }, }); ``` @@ -146,7 +146,6 @@ Error in connector: Error querying the database: db error: FATAL: sorry, too man at PrismaClientFetcher.request ``` - ## Use Prisma Client to send queries to your database Once you have instantiated `PrismaClient`, you can start sending queries in your code: @@ -167,6 +166,6 @@ const users = await prisma.user.findMany(); Whenever you make changes to your database that are reflected in the Prisma schema, you need to manually re-generate Prisma Client to update the generated code in your output directory: -```npm +```npm npx prisma generate ``` diff --git a/apps/docs/content/docs/orm/prisma-client/special-fields-and-types/working-with-composite-ids-and-constraints.mdx b/apps/docs/content/docs/orm/prisma-client/special-fields-and-types/working-with-composite-ids-and-constraints.mdx index 2225531ae1..83379fa72b 100644 --- a/apps/docs/content/docs/orm/prisma-client/special-fields-and-types/working-with-composite-ids-and-constraints.mdx +++ b/apps/docs/content/docs/orm/prisma-client/special-fields-and-types/working-with-composite-ids-and-constraints.mdx @@ -1,9 +1,9 @@ --- title: Working with compound IDs and unique constraints -description: 'How to read, write, and filter by compound IDs and unique constraints' +description: "How to read, write, and filter by compound IDs and unique constraints" url: /orm/prisma-client/special-fields-and-types/working-with-composite-ids-and-constraints metaTitle: Working with compound IDs and unique constraints (Concepts) -metaDescription: 'How to read, write, and filter by compound IDs and unique constraints.' +metaDescription: "How to read, write, and filter by compound IDs and unique constraints." --- Composite IDs and compound unique constraints can be defined in your Prisma schema using the [`@@id`](/orm/reference/prisma-schema-reference) and [`@@unique`](/orm/reference/prisma-schema-reference) attributes. diff --git a/apps/docs/content/docs/orm/prisma-client/special-fields-and-types/working-with-geometry-fields.mdx b/apps/docs/content/docs/orm/prisma-client/special-fields-and-types/working-with-geometry-fields.mdx index 75e7134993..004cb752ab 100644 --- a/apps/docs/content/docs/orm/prisma-client/special-fields-and-types/working-with-geometry-fields.mdx +++ b/apps/docs/content/docs/orm/prisma-client/special-fields-and-types/working-with-geometry-fields.mdx @@ -549,9 +549,7 @@ const availableZones = await prisma.deliveryZone.findMany({ const canDeliver = availableZones.length > 0; if (canDeliver) { - console.log( - `Delivery available in zones: ${availableZones.map((z) => z.name).join(", ")}` - ); + console.log(`Delivery available in zones: ${availableZones.map((z) => z.name).join(", ")}`); } else { console.log("Sorry, we don't deliver to this address yet"); } @@ -764,15 +762,14 @@ If you're using **PostgreSQL 16** with datasets containing thousands of records, **Workaround:** Use the `withSpatialOptimization` helper from `@prisma/adapter-pg`: ```typescript -import { PrismaClient } from '@prisma/client' -import { withSpatialOptimization } from '@prisma/adapter-pg' +import { PrismaClient } from "@prisma/client"; +import { withSpatialOptimization } from "@prisma/adapter-pg"; -const userLocation = [13.4, 52.5] as [number, number] +const userLocation = [13.4, 52.5] as [number, number]; // The helper automatically detects PostgreSQL 16 and applies optimization only when needed -const nearby = await withSpatialOptimization( - prisma, - (client) => client.location.findMany({ +const nearby = await withSpatialOptimization(prisma, (client) => + client.location.findMany({ where: { position: { near: { @@ -787,26 +784,26 @@ const nearby = await withSpatialOptimization( }, }, take: 20, - }) -) + }), +); // Works with all spatial filters: -const zonesContaining = await withSpatialOptimization( - prisma, - (client) => client.deliveryZone.findMany({ +const zonesContaining = await withSpatialOptimization(prisma, (client) => + client.deliveryZone.findMany({ where: { boundary: { intersects: { - type: 'Point', + type: "Point", coordinates: userLocation, }, }, }, - }) -) + }), +); ``` The helper: + - **Automatically detects** PostgreSQL version (cached for performance) - **Only applies optimization** on PostgreSQL 16 - **Skips optimization** on PostgreSQL 17+ (where the bug is fixed) diff --git a/apps/docs/content/docs/orm/prisma-client/special-fields-and-types/working-with-json-fields.mdx b/apps/docs/content/docs/orm/prisma-client/special-fields-and-types/working-with-json-fields.mdx index 06c752bf63..7ffa67fa68 100644 --- a/apps/docs/content/docs/orm/prisma-client/special-fields-and-types/working-with-json-fields.mdx +++ b/apps/docs/content/docs/orm/prisma-client/special-fields-and-types/working-with-json-fields.mdx @@ -1,9 +1,9 @@ --- title: Working with Json fields -description: 'How to read, write, and filter by Json fields' +description: "How to read, write, and filter by Json fields" url: /orm/prisma-client/special-fields-and-types/working-with-json-fields metaTitle: Working with Json fields (Concepts) -metaDescription: 'How to read, write, and filter by Json fields.' +metaDescription: "How to read, write, and filter by Json fields." --- Use the [`Json`](/orm/reference/prisma-schema-reference#json) Prisma ORM field type to read, write, and perform basic filtering on JSON types in the underlying database. In the following example, the `User` model has an optional `Json` field named `extendedPetsData`: diff --git a/apps/docs/content/docs/orm/prisma-client/special-fields-and-types/working-with-scalar-lists-arrays.mdx b/apps/docs/content/docs/orm/prisma-client/special-fields-and-types/working-with-scalar-lists-arrays.mdx index eeb3d30d04..6629bf1f35 100644 --- a/apps/docs/content/docs/orm/prisma-client/special-fields-and-types/working-with-scalar-lists-arrays.mdx +++ b/apps/docs/content/docs/orm/prisma-client/special-fields-and-types/working-with-scalar-lists-arrays.mdx @@ -1,9 +1,9 @@ --- title: Working with scalar lists -description: 'How to read, write, and filter by scalar lists / arrays' +description: "How to read, write, and filter by scalar lists / arrays" url: /orm/prisma-client/special-fields-and-types/working-with-scalar-lists-arrays metaTitle: Working with scalar lists/arrays (Concepts) -metaDescription: 'How to read, write, and filter by scalar lists / arrays.' +metaDescription: "How to read, write, and filter by scalar lists / arrays." --- [Scalar lists](/orm/reference/prisma-schema-reference#-modifier) are represented by the `[]` modifier and are only available if the underlying database supports scalar lists. The following example has one scalar `String` list named `pets`: diff --git a/apps/docs/content/docs/orm/prisma-client/type-safety/index.mdx b/apps/docs/content/docs/orm/prisma-client/type-safety/index.mdx index 30af15e6ac..f47a592796 100644 --- a/apps/docs/content/docs/orm/prisma-client/type-safety/index.mdx +++ b/apps/docs/content/docs/orm/prisma-client/type-safety/index.mdx @@ -1,10 +1,9 @@ --- title: Type safety Overview -description: 'Prisma Client provides full type safety for queries, even for partial queries or included relations. This page explains how to leverage the generated types and utilities' +description: "Prisma Client provides full type safety for queries, even for partial queries or included relations. This page explains how to leverage the generated types and utilities" url: /orm/prisma-client/type-safety -metaTitle: 'Type safety' -metaDescription: 'Prisma Client provides full type safety for queries, even for partial queries or included relations. This page explains how to leverage the generated types and utilities.' - +metaTitle: "Type safety" +metaDescription: "Prisma Client provides full type safety for queries, even for partial queries or included relations. This page explains how to leverage the generated types and utilities." --- The generated code for Prisma Client contains several helpful types and utilities that you can use to make your application more type-safe. This page describes patterns for leveraging them. diff --git a/apps/docs/content/docs/orm/prisma-client/type-safety/meta.json b/apps/docs/content/docs/orm/prisma-client/type-safety/meta.json index 13121953c5..9560945dab 100644 --- a/apps/docs/content/docs/orm/prisma-client/type-safety/meta.json +++ b/apps/docs/content/docs/orm/prisma-client/type-safety/meta.json @@ -1,8 +1,4 @@ { "title": "Type Safety", - "pages": [ - "index", - "prisma-type-system", - "operating-against-partial-structures-of-model-types" - ] + "pages": ["index", "prisma-type-system", "operating-against-partial-structures-of-model-types"] } diff --git a/apps/docs/content/docs/orm/prisma-client/using-raw-sql/index.mdx b/apps/docs/content/docs/orm/prisma-client/using-raw-sql/index.mdx index 7c276c51f1..8c80907632 100644 --- a/apps/docs/content/docs/orm/prisma-client/using-raw-sql/index.mdx +++ b/apps/docs/content/docs/orm/prisma-client/using-raw-sql/index.mdx @@ -14,7 +14,6 @@ In most cases, [TypedSQL](#writing-type-safe-queries-with-prisma-client-and-type ## Writing type-safe queries with Prisma Client and TypedSQL - ### What is TypedSQL? TypedSQL is a new feature of Prisma ORM that allows you to write your queries in `.sql` files while still enjoying the great developer experience of Prisma Client. You can write the code you're comfortable with and benefit from fully-typed inputs and outputs. diff --git a/apps/docs/content/docs/orm/prisma-client/using-raw-sql/raw-queries.mdx b/apps/docs/content/docs/orm/prisma-client/using-raw-sql/raw-queries.mdx index 7cd439372d..65402c1bc6 100644 --- a/apps/docs/content/docs/orm/prisma-client/using-raw-sql/raw-queries.mdx +++ b/apps/docs/content/docs/orm/prisma-client/using-raw-sql/raw-queries.mdx @@ -332,7 +332,6 @@ $executeRawUnsafe(query: string, ...values: any[]): PrismaPromise prisma/migrations/0_init/migration.sql - ``` +```npm +npx prisma migrate diff \ + --from-empty \ + --to-schema prisma/schema.prisma \ + --script > prisma/migrations/0_init/migration.sql +``` - Run the `prisma migrate resolve` command for each migration that should be ignored: - ```npm - npx prisma migrate resolve --applied 0_init - ``` +```npm +npx prisma migrate resolve --applied 0_init +``` This command adds the target migration to the `_prisma_migrations` table and marks it as applied. When you run `prisma migrate deploy` to apply new migrations, Prisma Migrate: diff --git a/apps/docs/content/docs/orm/prisma-migrate/workflows/development-and-production.mdx b/apps/docs/content/docs/orm/prisma-migrate/workflows/development-and-production.mdx index 9543aa40c7..973f62b916 100644 --- a/apps/docs/content/docs/orm/prisma-migrate/workflows/development-and-production.mdx +++ b/apps/docs/content/docs/orm/prisma-migrate/workflows/development-and-production.mdx @@ -93,10 +93,11 @@ This command: - Compares applied migrations against the migration history and **warns** if any migrations have been modified: - ```bash - WARNING The following migrations have been modified since they were applied: - 20210313140442_favorite_colors - ``` + ```bash + WARNING The following migrations have been modified since they were applied: + 20210313140442_favorite_colors + ``` + - Applies pending migrations The `migrate deploy` command: diff --git a/apps/docs/content/docs/orm/prisma-migrate/workflows/generating-down-migrations.mdx b/apps/docs/content/docs/orm/prisma-migrate/workflows/generating-down-migrations.mdx index c07d19e56b..db5a658d38 100644 --- a/apps/docs/content/docs/orm/prisma-migrate/workflows/generating-down-migrations.mdx +++ b/apps/docs/content/docs/orm/prisma-migrate/workflows/generating-down-migrations.mdx @@ -79,14 +79,12 @@ You will need to create the down migration first, before creating the correspond ``` - Generate the SQL file for the down migration. To do this, you will use `migrate diff` to make a comparison: - - from the newly edited schema - to the state of the schema after the last migration and output this to a SQL script, `down.sql`. There are two potential options for specifying the 'to' state: - - Using `--to-migrations`: this makes a comparison to the state of the migrations given in the migrations directory. This is the preferred option, as it is more robust. To use this option, run: ```npm diff --git a/apps/docs/content/docs/orm/prisma-migrate/workflows/patching-and-hotfixing.mdx b/apps/docs/content/docs/orm/prisma-migrate/workflows/patching-and-hotfixing.mdx index 50fde1d6b3..d9420b679b 100644 --- a/apps/docs/content/docs/orm/prisma-migrate/workflows/patching-and-hotfixing.mdx +++ b/apps/docs/content/docs/orm/prisma-migrate/workflows/patching-and-hotfixing.mdx @@ -239,7 +239,6 @@ This will mark the failed migration called 'Unique' in the migrations table on y Your local migration history now yields the same result as the state your production environment is in. You can now continue using the already known `migrate dev` /`migrate deploy` workflow. - ## Prisma Migrate and PgBouncer You might see the following error if you attempt to run Prisma Migrate commands in an environment that uses PgBouncer for connection pooling: diff --git a/apps/docs/content/docs/orm/prisma-migrate/workflows/prototyping-your-schema.mdx b/apps/docs/content/docs/orm/prisma-migrate/workflows/prototyping-your-schema.mdx index 668d5d57ae..6fba169806 100644 --- a/apps/docs/content/docs/orm/prisma-migrate/workflows/prototyping-your-schema.mdx +++ b/apps/docs/content/docs/orm/prisma-migrate/workflows/prototyping-your-schema.mdx @@ -13,13 +13,14 @@ The Prisma CLI has a dedicated command for prototyping schemas: [`db push`](/orm - Introspects the database to infer and executes the changes required to make your database schema reflect the state of your Prisma schema. - Does not automatically trigger generators (for example, Prisma Client). You need to manually invoke `prisma generate` after making schema changes. - If `db push` anticipates that the changes could result in data loss, it will: - - Throw an error - - Require the `--accept-data-loss` option if you still want to make the changes + - Throw an error + - Require the `--accept-data-loss` option if you still want to make the changes :::info[Notes] - - `db push` does not interact with or rely on migrations. The migrations table `_prisma_migrations` will not be created or updated, and no migration files will be generated. - - When working with PlanetScale, we recommend that you use `db push` instead of `migrate`. For details refer to our Getting started documentation, either [Start from scratch guide](/prisma-orm/quickstart/planetscale) or [Add to existing project guide](/prisma-orm/add-to-existing-project/planetscale) depending on your situation. -::: + +- `db push` does not interact with or rely on migrations. The migrations table `_prisma_migrations` will not be created or updated, and no migration files will be generated. +- When working with PlanetScale, we recommend that you use `db push` instead of `migrate`. For details refer to our Getting started documentation, either [Start from scratch guide](/prisma-orm/quickstart/planetscale) or [Add to existing project guide](/prisma-orm/add-to-existing-project/planetscale) depending on your situation. + ::: ## Choosing `db push` or Prisma Migrate @@ -49,103 +50,103 @@ Yes, you can [use `db push` and Prisma Migrate together in your development work The following scenario demonstrates how to use `db push` to synchronize a new schema with an empty database, and evolve that schema - including what happens when `db push` detects that a change will result in data loss. -- Create a first draft of your schema: - - ```prisma title="schema.prisma" - generator client { - provider = "prisma-client" - output = "./generated" - } - - datasource db { - provider = "postgresql" - } - - model User { - id Int @id @default(autoincrement()) - name String - jobTitle String - posts Post[] - profile Profile? - } - - model Profile { - id Int @id @default(autoincrement()) - biograpy String // Intentional typo! - userId Int @unique - user User @relation(fields: [userId], references: [id]) - } - - model Post { - id Int @id @default(autoincrement()) - title String - published Boolean @default(true) - content String @db.VarChar(500) - authorId Int - author User @relation(fields: [authorId], references: [id]) - categories Category[] - } - - model Category { - id Int @id @default(autoincrement()) - name String @db.VarChar(50) - posts Post[] - - @@unique([name]) - } - ``` - -- Use `db push` to push the initial schema to the database: - - ```npm - npx prisma db push - ``` - -- Create some example content: - - ```ts title="main.ts" - const add = await prisma.user.create({ - data: { - name: "Eloise", - jobTitle: "Programmer", - posts: { - create: { - title: "How to create a MySQL database", - content: "Some content", - }, +- Create a first draft of your schema: + + ```prisma title="schema.prisma" + generator client { + provider = "prisma-client" + output = "./generated" + } + + datasource db { + provider = "postgresql" + } + + model User { + id Int @id @default(autoincrement()) + name String + jobTitle String + posts Post[] + profile Profile? + } + + model Profile { + id Int @id @default(autoincrement()) + biograpy String // Intentional typo! + userId Int @unique + user User @relation(fields: [userId], references: [id]) + } + + model Post { + id Int @id @default(autoincrement()) + title String + published Boolean @default(true) + content String @db.VarChar(500) + authorId Int + author User @relation(fields: [authorId], references: [id]) + categories Category[] + } + + model Category { + id Int @id @default(autoincrement()) + name String @db.VarChar(50) + posts Post[] + + @@unique([name]) + } + ``` + +- Use `db push` to push the initial schema to the database: + + ```npm + npx prisma db push + ``` + +- Create some example content: + + ```ts title="main.ts" + const add = await prisma.user.create({ + data: { + name: "Eloise", + jobTitle: "Programmer", + posts: { + create: { + title: "How to create a MySQL database", + content: "Some content", }, }, - }); - ``` + }, + }); + ``` -- Make an additive change - for example, create a new required field: +- Make an additive change - for example, create a new required field: - ```prisma highlight=6;add title="schema.prisma" - model Post { - id Int @id @default(autoincrement()) - title String - description String // [!code ++] - published Boolean @default(true) - content String @db.VarChar(500) - authorId Int - author User @relation(fields: [authorId], references: [id]) - categories Category[] - } - ``` + ```prisma highlight=6;add title="schema.prisma" + model Post { + id Int @id @default(autoincrement()) + title String + description String // [!code ++] + published Boolean @default(true) + content String @db.VarChar(500) + authorId Int + author User @relation(fields: [authorId], references: [id]) + categories Category[] + } + ``` -- Push the changes: +- Push the changes: - ```npm - npx prisma db push - ``` + ```npm + npx prisma db push + ``` - `db push` will fail because you cannot add a required field to a table with existing content unless you provide a default value. + `db push` will fail because you cannot add a required field to a table with existing content unless you provide a default value. -- Reset **all data** in your database and re-apply migrations. +- Reset **all data** in your database and re-apply migrations. - ```npm - npx prisma migrate reset - ``` + ```npm + npx prisma migrate reset + ``` :::info[Note] Unlike Prisma Migrate, `db push` does not generate migrations that you can modify to preserve data, and is therefore best suited for prototyping in a development environment. @@ -153,15 +154,15 @@ Unlike Prisma Migrate, `db push` does not generate migrations that you can modif - Continue to evolve your schema until it reaches a relatively stable state. -- Initialize a migration history: +- Initialize a migration history: - ```npm - npx prisma migrate dev --name initial-state - ``` + ```npm + npx prisma migrate dev --name initial-state + ``` - The steps taken to reach the initial prototype are not preserved - `db push` does not generate a history. + The steps taken to reach the initial prototype are not preserved - `db push` does not generate a history. -- Push your migration history and Prisma schema to source control (e.g. Git). +- Push your migration history and Prisma schema to source control (e.g. Git). At this point, the final draft of your prototyping is preserved in a migration and can be pushed to other environments (testing, production, or other members of your team). @@ -171,93 +172,93 @@ The following scenario demonstrates how to use `db push` to prototype a change t - Check out the latest Prisma schema and migration history: - ```prisma title="schema.prisma" - generator client { - provider = "prisma-client" - output = "./generated" - } - - datasource db { - provider = "postgresql" - } - - model User { - id Int @id @default(autoincrement()) - name String - jobTitle String - posts Post[] - profile Profile? - } - - model Profile { - id Int @id @default(autoincrement()) - biograpy String // Intentional typo! - userId Int @unique - user User @relation(fields: [userId], references: [id]) - } - - model Post { - id Int @id @default(autoincrement()) - title String - published Boolean @default(true) - content String @db.VarChar(500) - authorId Int - author User @relation(fields: [authorId], references: [id]) - categories Category[] - } - - model Category { - id Int @id @default(autoincrement()) - name String @db.VarChar(50) - posts Post[] - - @@unique([name]) - } - ``` + ```prisma title="schema.prisma" + generator client { + provider = "prisma-client" + output = "./generated" + } + + datasource db { + provider = "postgresql" + } + + model User { + id Int @id @default(autoincrement()) + name String + jobTitle String + posts Post[] + profile Profile? + } + + model Profile { + id Int @id @default(autoincrement()) + biograpy String // Intentional typo! + userId Int @unique + user User @relation(fields: [userId], references: [id]) + } + + model Post { + id Int @id @default(autoincrement()) + title String + published Boolean @default(true) + content String @db.VarChar(500) + authorId Int + author User @relation(fields: [authorId], references: [id]) + categories Category[] + } + + model Category { + id Int @id @default(autoincrement()) + name String @db.VarChar(50) + posts Post[] + + @@unique([name]) + } + ``` - Prototype your new feature, which can involve any number of steps. For example, you might: - - Create a `tags String[]` field, then run `db push` - - Change the field type to `tags Tag[]` and add a new model named `Tag`, then run `db push` - - Change your mind and restore the original `tags String[]` field, then call `db push` - - Make a manual change to the `tags` field in the database - for example, adding a constraint - - After experimenting with several solutions, the final schema change looks like this: - - ```prisma title="schema.prisma" - model Post { - id Int @id @default(autoincrement()) - title String - description String - published Boolean @default(true) - content String @db.VarChar(500) - authorId Int - author User @relation(fields: [authorId], references: [id]) - categories Category[] - tags String[] - } - ``` + - Create a `tags String[]` field, then run `db push` + - Change the field type to `tags Tag[]` and add a new model named `Tag`, then run `db push` + - Change your mind and restore the original `tags String[]` field, then call `db push` + - Make a manual change to the `tags` field in the database - for example, adding a constraint + + After experimenting with several solutions, the final schema change looks like this: + + ```prisma title="schema.prisma" + model Post { + id Int @id @default(autoincrement()) + title String + description String + published Boolean @default(true) + content String @db.VarChar(500) + authorId Int + author User @relation(fields: [authorId], references: [id]) + categories Category[] + tags String[] + } + ``` - To create a migration that adds the new `tags` field, run the `migrate dev` command: - ```npm - npx prisma migrate dev --name added-tags - ``` + ```npm + npx prisma migrate dev --name added-tags + ``` - Prisma Migrate will prompt you to reset because the changes you made manually and with `db push` while prototyping are not part of the migration history: + Prisma Migrate will prompt you to reset because the changes you made manually and with `db push` while prototyping are not part of the migration history: - ```bash - √ Drift detected: Your database schema is not in sync with your migration history. + ```bash + √ Drift detected: Your database schema is not in sync with your migration history. - We need to reset the PostgreSQL database "prototyping" at "localhost:5432". - ``` + We need to reset the PostgreSQL database "prototyping" at "localhost:5432". + ``` - :::warning - This will result in total data loss. - ::: +:::warning +This will result in total data loss. +::: - ```npm - npx prisma migrate reset - ``` +```npm +npx prisma migrate reset +``` - Prisma Migrate replays the existing migration history, generates a new migration based on your schema changes, and applies those changes to the database. diff --git a/apps/docs/content/docs/orm/prisma-migrate/workflows/squashing-migrations.mdx b/apps/docs/content/docs/orm/prisma-migrate/workflows/squashing-migrations.mdx index fe984d2d3c..4c62703ecf 100644 --- a/apps/docs/content/docs/orm/prisma-migrate/workflows/squashing-migrations.mdx +++ b/apps/docs/content/docs/orm/prisma-migrate/workflows/squashing-migrations.mdx @@ -83,17 +83,17 @@ Then follow these steps, either on your `main` branch or on a newly checked out - Create a new empty directory in the `./prisma/migrations` directory. In this guide this will be called `000000000000_squashed_migrations`. Inside this, add a new empty `migration.sql` file. - :::info - We name the migration `000000000000_squashed_migrations` with all the leading zeroes because we want it to be the first migration in the migrations directory. Migrate runs the migrations in the directory in lexicographic (alphabetical) order. This is why it generates migrations with the date and time as a prefix when you use `migrate dev`. You can give the migration another name, as long as it it sorts lower than later migrations, for example `0_squashed` or `202207180000_squashed`. +:::info +We name the migration `000000000000_squashed_migrations` with all the leading zeroes because we want it to be the first migration in the migrations directory. Migrate runs the migrations in the directory in lexicographic (alphabetical) order. This is why it generates migrations with the date and time as a prefix when you use `migrate dev`. You can give the migration another name, as long as it it sorts lower than later migrations, for example `0_squashed` or `202207180000_squashed`. - ::: +::: - Create a single migration that takes you: - from an empty database - to the current state of the production database schema as described in your `./prisma/schema.prisma` file - and outputs this to the `migration.sql` file created above - You can do this using the `migrate diff` command. From the root directory of your project, run the following command: + You can do this using the `migrate diff` command. From the root directory of your project, run the following command: ```npm npx prisma migrate diff \ @@ -104,7 +104,7 @@ npx prisma migrate diff \ - Mark this migration as having been applied on production, to prevent it from being run there: - You can do this using the [`migrate resolve`](/orm/reference/prisma-cli-reference#migrate-resolve) command to mark the migration in the `000000000000_squashed_migrations` directory as already applied: + You can do this using the [`migrate resolve`](/orm/reference/prisma-cli-reference#migrate-resolve) command to mark the migration in the `000000000000_squashed_migrations` directory as already applied: ```npm npx prisma migrate resolve \ diff --git a/apps/docs/content/docs/orm/prisma-migrate/workflows/troubleshooting.mdx b/apps/docs/content/docs/orm/prisma-migrate/workflows/troubleshooting.mdx index a0999ce52f..323bee9264 100644 --- a/apps/docs/content/docs/orm/prisma-migrate/workflows/troubleshooting.mdx +++ b/apps/docs/content/docs/orm/prisma-migrate/workflows/troubleshooting.mdx @@ -69,11 +69,11 @@ If you made manual changes to the database that you want to keep, you can: - Introspect the database: - ```npm - npx prisma db pull - ``` +```npm +npx prisma db pull +``` - Prisma will update your schema with the changes made directly in the database. +Prisma will update your schema with the changes made directly in the database. - Generate a new migration to include the introspected changes in your migration history: @@ -110,12 +110,13 @@ npx prisma migrate reset - Delete the `migration.sql` file. - Modify the schema - for example, add a default value to the mandatory field. - Migrate: - ```npm - npx prisma migrate dev - ``` - Prisma Migrate will prompt you to reset the database and re-apply all migrations. + `npm +npx prisma migrate dev +` + Prisma Migrate will prompt you to reset the database and re-apply all migrations. - If something interrupted the migration process, reset the database: + ```npm npx prisma migrate reset ``` diff --git a/apps/docs/content/docs/orm/prisma-migrate/workflows/unsupported-database-features.mdx b/apps/docs/content/docs/orm/prisma-migrate/workflows/unsupported-database-features.mdx index 84b8b0b8d3..a31d5dfedf 100644 --- a/apps/docs/content/docs/orm/prisma-migrate/workflows/unsupported-database-features.mdx +++ b/apps/docs/content/docs/orm/prisma-migrate/workflows/unsupported-database-features.mdx @@ -2,7 +2,7 @@ title: Unsupported database features (Prisma Migrate) description: How to include unsupported database features for projects that use Prisma Migrate url: /orm/prisma-migrate/workflows/unsupported-database-features -metaTitle: 'Prisma Migrate: Unsupported database features' +metaTitle: "Prisma Migrate: Unsupported database features" metaDescription: How to include unsupported database features for projects that use Prisma Migrate. --- diff --git a/apps/docs/content/docs/orm/prisma-schema/data-model/database-mapping.mdx b/apps/docs/content/docs/orm/prisma-schema/data-model/database-mapping.mdx index 1280815955..bd8471f441 100644 --- a/apps/docs/content/docs/orm/prisma-schema/data-model/database-mapping.mdx +++ b/apps/docs/content/docs/orm/prisma-schema/data-model/database-mapping.mdx @@ -103,7 +103,6 @@ You can optionally use the `map` argument to explicitly define the **underlying When introspecting a database, the `map` argument will _only_ be rendered in the schema if the name _differs_ from Prisma ORM's [default constraint naming convention for indexes and constraints](#prisma-orms-default-naming-conventions-for-indexes-and-constraints). - ### Use cases for named constraints Some use cases for explicitly named constraints include: diff --git a/apps/docs/content/docs/orm/prisma-schema/data-model/indexes.mdx b/apps/docs/content/docs/orm/prisma-schema/data-model/indexes.mdx index 37937907d1..a1d9a54325 100644 --- a/apps/docs/content/docs/orm/prisma-schema/data-model/indexes.mdx +++ b/apps/docs/content/docs/orm/prisma-schema/data-model/indexes.mdx @@ -32,7 +32,6 @@ You can configure indexes, unique constraints, and primary key constraints with - Available on the `@id`, `@@id`, `@unique`, `@@unique` and `@@index` attributes - Supported in all databases - ### Configuring the length of indexes with `length` (MySQL) The `length` argument is specific to MySQL and allows you to define indexes and constraints on columns of `String` and `Byte` types. For these types, MySQL requires you to specify a maximum length for the subpart of the value to be indexed in cases where the full value would exceed MySQL's limits for index sizes. See [the MySQL documentation](https://dev.mysql.com/doc/refman/8.0/en/innodb-limits.html) for more details. @@ -88,7 +87,6 @@ The `sort` argument allows you to specify the order that the entries of the inde - In PostgreSQL, sort order can only be specified on indexes, not on unique constraints - In SQL Server, sort order is supported on all constraints and indexes including `@id` and `@@id` - For example, in MySQL/MariaDB, the following table using a descending unique constraint: ```sql @@ -713,4 +711,3 @@ model Post { @@fulltext([title(sort: Desc), content]) } ``` - diff --git a/apps/docs/content/docs/orm/prisma-schema/data-model/models.mdx b/apps/docs/content/docs/orm/prisma-schema/data-model/models.mdx index 78fda3dbcb..31092c702e 100644 --- a/apps/docs/content/docs/orm/prisma-schema/data-model/models.mdx +++ b/apps/docs/content/docs/orm/prisma-schema/data-model/models.mdx @@ -1,9 +1,9 @@ --- title: Models -description: 'Learn about the concepts for building your data model with Prisma: Models, scalar types, enums, attributes, functions, IDs, default values and more' +description: "Learn about the concepts for building your data model with Prisma: Models, scalar types, enums, attributes, functions, IDs, default values and more" url: /orm/prisma-schema/data-model/models metaTitle: Models -metaDescription: 'Learn about the concepts for building your data model with Prisma: Models, scalar types, enums, attributes, functions, IDs, default values and more.' +metaDescription: "Learn about the concepts for building your data model with Prisma: Models, scalar types, enums, attributes, functions, IDs, default values and more." --- The data model definition part of the [Prisma schema](/orm/prisma-schema/overview) defines your application models (also called **Prisma models**). Models: diff --git a/apps/docs/content/docs/orm/prisma-schema/data-model/relations/index.mdx b/apps/docs/content/docs/orm/prisma-schema/data-model/relations/index.mdx index cb85698288..c63ee62b3f 100644 --- a/apps/docs/content/docs/orm/prisma-schema/data-model/relations/index.mdx +++ b/apps/docs/content/docs/orm/prisma-schema/data-model/relations/index.mdx @@ -1,9 +1,9 @@ --- title: Relations -description: 'A relation is a connection between two models in the Prisma schema. This page explains how you can define one-to-one, one-to-many and many-to-many relations in Prisma' +description: "A relation is a connection between two models in the Prisma schema. This page explains how you can define one-to-one, one-to-many and many-to-many relations in Prisma" url: /orm/prisma-schema/data-model/relations metaTitle: Relations -metaDescription: 'A relation is a connection between two models in the Prisma schema. This page explains how you can define one-to-one, one-to-many and many-to-many relations in Prisma.' +metaDescription: "A relation is a connection between two models in the Prisma schema. This page explains how you can define one-to-one, one-to-many and many-to-many relations in Prisma." --- A relation is a _connection_ between two models in the Prisma schema. For example, there is a one-to-many relation between `User` and `Post` because one user can have many blog posts: diff --git a/apps/docs/content/docs/orm/prisma-schema/data-model/relations/referential-actions.mdx b/apps/docs/content/docs/orm/prisma-schema/data-model/relations/referential-actions.mdx index 48998e0efa..e6421b08e4 100644 --- a/apps/docs/content/docs/orm/prisma-schema/data-model/relations/referential-actions.mdx +++ b/apps/docs/content/docs/orm/prisma-schema/data-model/relations/referential-actions.mdx @@ -3,7 +3,7 @@ title: Referential actions description: Referential actions let you define the update and delete behavior of related models on the database level url: /orm/prisma-schema/data-model/relations/referential-actions metaTitle: Special rules for referential actions in SQL Server and MongoDB -metaDescription: 'Circular references or multiple cascade paths can cause validation errors on Microsoft SQL Server and MongoDB. Since the database does not handle these situations out of the box, learn how to solve this problem.' +metaDescription: "Circular references or multiple cascade paths can cause validation errors on Microsoft SQL Server and MongoDB. Since the database does not handle these situations out of the box, learn how to solve this problem." --- Referential actions determine what happens to a record when your application deletes or updates a related record. They are defined in the [`@relation`](/orm/reference/prisma-schema-reference#relation) attribute and map to foreign key constraints in the database. @@ -38,13 +38,12 @@ If you do not specify a referential action, Prisma ORM [uses a default](#referen - ## Available referential actions Prisma ORM supports five referential actions: - **[`Cascade`](#cascade)** - Deletes/updates cascade to related records -- **[`Restrict`](#restrict)** - Prevents deletion/update if related records exist +- **[`Restrict`](#restrict)** - Prevents deletion/update if related records exist - **[`NoAction`](#noaction)** - Similar to Restrict, behavior varies by database - **[`SetNull`](#setnull)** - Sets foreign key to NULL (requires optional relation) - **[`SetDefault`](#setdefault)** - Sets foreign key to default value diff --git a/apps/docs/content/docs/orm/prisma-schema/data-model/relations/relation-mode.mdx b/apps/docs/content/docs/orm/prisma-schema/data-model/relations/relation-mode.mdx index 44c38a38f0..542f275d76 100644 --- a/apps/docs/content/docs/orm/prisma-schema/data-model/relations/relation-mode.mdx +++ b/apps/docs/content/docs/orm/prisma-schema/data-model/relations/relation-mode.mdx @@ -88,7 +88,6 @@ datasource db { } ``` - For relational databases, the available options are: - `foreignKeys`: this handles relations in the database with foreign keys. This is the default option for all relational database connectors and is active if no `relationMode` is explicitly set in the `datasource` block. diff --git a/apps/docs/content/docs/orm/prisma-schema/data-model/unsupported-database-features.mdx b/apps/docs/content/docs/orm/prisma-schema/data-model/unsupported-database-features.mdx index d917f69499..4de83be8a8 100644 --- a/apps/docs/content/docs/orm/prisma-schema/data-model/unsupported-database-features.mdx +++ b/apps/docs/content/docs/orm/prisma-schema/data-model/unsupported-database-features.mdx @@ -2,7 +2,7 @@ title: Unsupported database features (Prisma Schema) description: How to support database features that do not have an equivalent syntax in Prisma Schema Language url: /orm/prisma-schema/data-model/unsupported-database-features -metaTitle: 'Prisma schema: Unsupported database features' +metaTitle: "Prisma schema: Unsupported database features" metaDescription: How to support database features that do not have an equivalent syntax in Prisma Schema Language. --- diff --git a/apps/docs/content/docs/orm/prisma-schema/meta.json b/apps/docs/content/docs/orm/prisma-schema/meta.json index 5b12d19340..0021a6a04d 100644 --- a/apps/docs/content/docs/orm/prisma-schema/meta.json +++ b/apps/docs/content/docs/orm/prisma-schema/meta.json @@ -1,10 +1,5 @@ { "title": "Prisma Schema", "defaultOpen": true, - "pages": [ - "overview", - "data-model", - "introspection", - "postgresql-extensions" - ] + "pages": ["overview", "data-model", "introspection", "postgresql-extensions"] } diff --git a/apps/docs/content/docs/orm/prisma-schema/overview/data-sources.mdx b/apps/docs/content/docs/orm/prisma-schema/overview/data-sources.mdx index a906a86072..65b5f3c2da 100644 --- a/apps/docs/content/docs/orm/prisma-schema/overview/data-sources.mdx +++ b/apps/docs/content/docs/orm/prisma-schema/overview/data-sources.mdx @@ -19,7 +19,6 @@ A Prisma schema can only have _one_ data source. However, you can: - [Override the database connection when creating your `PrismaClient`](/orm/reference/prisma-client-reference) - [Specify a different **database** for Prisma Migrate's shadow database if you are working with cloud-hosted development databases](/orm/prisma-migrate/understanding-prisma-migrate/shadow-database#cloud-hosted-shadow-databases-must-be-created-manually) - ## Securing database connections Some data source `provider`s allow you to configure your connection with SSL/TLS **by specifying certificate locations in your connection configuration**. diff --git a/apps/docs/content/docs/orm/prisma-schema/overview/generators.mdx b/apps/docs/content/docs/orm/prisma-schema/overview/generators.mdx index 405b53dc04..afce2d24c8 100644 --- a/apps/docs/content/docs/orm/prisma-schema/overview/generators.mdx +++ b/apps/docs/content/docs/orm/prisma-schema/overview/generators.mdx @@ -215,7 +215,6 @@ For using types in your frontend (i.e. code that runs in the browser). - Contains all model and enum types and values. - Provides access to various utilities like `Prisma.JsonNull` and `Prisma.Decimal`. - Example: ```ts title="src/index.ts" diff --git a/apps/docs/content/docs/orm/prisma-schema/overview/index.mdx b/apps/docs/content/docs/orm/prisma-schema/overview/index.mdx index bca8342209..4df16829c1 100644 --- a/apps/docs/content/docs/orm/prisma-schema/overview/index.mdx +++ b/apps/docs/content/docs/orm/prisma-schema/overview/index.mdx @@ -32,7 +32,6 @@ The following is an example of a Prisma Schema that specifies: - A data model definition with two models (with one relation) and one `enum` - Several [native data type attributes](/orm/prisma-schema/data-model/models#native-types-mapping) (`@db.VarChar(255)`, `@db.ObjectId`) - ```prisma tab="Relational Databases" datasource db { provider = "postgresql" @@ -68,7 +67,6 @@ enum Role { } ``` - ```prisma tab="MongoDB" datasource db { provider = "mongodb" @@ -104,7 +102,6 @@ enum Role { } ``` - ## Syntax Prisma Schema files are written in Prisma Schema Language (PSL). See the [data sources](/orm/prisma-schema/overview/data-sources), [generators](/orm/prisma-schema/overview/generators), [data model definition](/orm/prisma-schema/data-model/models) and of course [Prisma Schema API reference](/orm/reference/prisma-schema-reference) pages for details and examples. diff --git a/apps/docs/content/docs/orm/prisma-schema/overview/location.mdx b/apps/docs/content/docs/orm/prisma-schema/overview/location.mdx index e8d3cdefa6..32d99e7fd0 100644 --- a/apps/docs/content/docs/orm/prisma-schema/overview/location.mdx +++ b/apps/docs/content/docs/orm/prisma-schema/overview/location.mdx @@ -50,10 +50,7 @@ Run prisma generate to generate Prisma Client. - + diff --git a/apps/docs/content/docs/orm/reference/connection-urls.mdx b/apps/docs/content/docs/orm/reference/connection-urls.mdx index c990159387..0419d6ec3c 100644 --- a/apps/docs/content/docs/orm/reference/connection-urls.mdx +++ b/apps/docs/content/docs/orm/reference/connection-urls.mdx @@ -1,9 +1,9 @@ --- title: Connection URLs -description: 'Learn about the format and syntax Prisma ORM uses for defining database connection URLs for PostgreSQL, MySQL and SQLite' +description: "Learn about the format and syntax Prisma ORM uses for defining database connection URLs for PostgreSQL, MySQL and SQLite" url: /orm/reference/connection-urls metaTitle: Connection URLs (Reference) -metaDescription: 'Learn about the format and syntax Prisma ORM uses for defining database connection URLs for PostgreSQL, MySQL and SQLite.' +metaDescription: "Learn about the format and syntax Prisma ORM uses for defining database connection URLs for PostgreSQL, MySQL and SQLite." --- Prisma ORM needs a connection URL to be able to connect to your database, e.g. when sending queries with [Prisma Client](/orm/prisma-client/setup-and-configuration/introduction) or when changing the database schema with [Prisma Migrate](/orm/prisma-migrate). @@ -81,7 +81,7 @@ When connecting via Prisma Accelerate, the connection string doesn't require a u ```ts title="prisma.config.ts" export default defineConfig({ datasource: { - url: "prisma+postgres://accelerate.prisma-data.net/?api_key=API_KEY" + url: "prisma+postgres://accelerate.prisma-data.net/?api_key=API_KEY", }, }); ``` @@ -91,7 +91,7 @@ In this snippet, `API_KEY` is a placeholder for the API key you are receiving wh ```ts title="prisma.config.ts" export default defineConfig({ datasource: { - url: "prisma+postgres://accelerate.prisma-data.net/?api_key=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcGlfa2V5IjoiMGNkZTFlMjQtNzhiYi00NTY4LTkyM2EtNWUwOTEzZWUyNjU1IiwidGVuYW50X2lkIjoiNzEyZWRlZTc1Y2U2MDk2ZjI4NDg3YjE4NWMyYzA2OTNhNGMxNzJkMjhhOWFlNGUwZTYxNWE4NWIxZWY1YjBkMCIsImludGVybmFsX3NlY3JldCI6IjA4MzQ2Y2RlLWI5ZjktNDQ4Yy04NThmLTMxNjg4ODEzNmEzZCJ9.N1Za6q6NfInzHvRkud6Ojt_-RFg18a0601vdYWGKOrk" + url: "prisma+postgres://accelerate.prisma-data.net/?api_key=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcGlfa2V5IjoiMGNkZTFlMjQtNzhiYi00NTY4LTkyM2EtNWUwOTEzZWUyNjU1IiwidGVuYW50X2lkIjoiNzEyZWRlZTc1Y2U2MDk2ZjI4NDg3YjE4NWMyYzA2OTNhNGMxNzJkMjhhOWFlNGUwZTYxNWE4NWIxZWY1YjBkMCIsImludGVybmFsX3NlY3JldCI6IjA4MzQ2Y2RlLWI5ZjktNDQ4Yy04NThmLTMxNjg4ODEzNmEzZCJ9.N1Za6q6NfInzHvRkud6Ojt_-RFg18a0601vdYWGKOrk", }, }); ``` @@ -103,7 +103,7 @@ The connection string for connecting to a [local Prisma Postgres](/postgres/data ```ts title="prisma.config.ts" export default defineConfig({ datasource: { - url: "prisma+postgres://accelerate.prisma-data.net/?api_key=API_KEY" + url: "prisma+postgres://accelerate.prisma-data.net/?api_key=API_KEY", }, }); ``` @@ -115,7 +115,7 @@ However, in this case the `API_KEY` doesn't provide authentication details. Inst ```ts title="prisma.config.ts" export default defineConfig({ datasource: { - url: "postgresql://janedoe:mypassword@localhost:5432/mydb?schema=sample" + url: "postgresql://janedoe:mypassword@localhost:5432/mydb?schema=sample", }, }); ``` @@ -125,7 +125,7 @@ export default defineConfig({ ```ts title="prisma.config.ts" export default defineConfig({ datasource: { - url: "mysql://janedoe:mypassword@localhost:3306/mydb" + url: "mysql://janedoe:mypassword@localhost:3306/mydb", }, }); ``` @@ -135,7 +135,7 @@ export default defineConfig({ ```ts title="prisma.config.ts" export default defineConfig({ datasource: { - url: "sqlserver://localhost:1433;initial catalog=sample;user=sa;password=mypassword;" + url: "sqlserver://localhost:1433;initial catalog=sample;user=sa;password=mypassword;", }, }); ``` @@ -145,7 +145,7 @@ export default defineConfig({ ```ts title="prisma.config.ts" export default defineConfig({ datasource: { - url: "file:./dev.db" + url: "file:./dev.db", }, }); ``` @@ -155,7 +155,7 @@ export default defineConfig({ ```ts title="prisma.config.ts" export default defineConfig({ datasource: { - url: "postgresql://janedoe:mypassword@localhost:26257/mydb?schema=public" + url: "postgresql://janedoe:mypassword@localhost:26257/mydb?schema=public", }, }); ``` diff --git a/apps/docs/content/docs/orm/reference/error-reference.mdx b/apps/docs/content/docs/orm/reference/error-reference.mdx index 6addd0391e..a430351b9b 100644 --- a/apps/docs/content/docs/orm/reference/error-reference.mdx +++ b/apps/docs/content/docs/orm/reference/error-reference.mdx @@ -1,9 +1,9 @@ --- title: Error Reference -description: 'Prisma Client, Migrate, and Introspection error codes' +description: "Prisma Client, Migrate, and Introspection error codes" url: /orm/reference/error-reference metaTitle: Errors -metaDescription: 'Prisma Client, Migrate, Introspection error message reference' +metaDescription: "Prisma Client, Migrate, Introspection error message reference" --- For more information about how to work with exceptions and error codes, see [Handling exceptions and errors](/orm/prisma-client/debugging-and-troubleshooting/handling-exceptions-and-errors). @@ -116,7 +116,7 @@ Prisma Client throws a `PrismaClientValidationError` exception if validation fai #### `P1012` -:::info +:::info If you get error code P1012 after you upgrade Prisma ORM to version 4.0.0 or later, see the [version 4.0.0 upgrade guide](/guides/upgrade-prisma-orm/v4#update-schema). A schema that was valid before version 4.0.0 might be invalid in version 4.0.0 and later. The upgrade guide explains how to update your schema to make it valid. ::: diff --git a/apps/docs/content/docs/orm/reference/errors/index.mdx b/apps/docs/content/docs/orm/reference/errors/index.mdx index 54c8228a91..1db91fb040 100644 --- a/apps/docs/content/docs/orm/reference/errors/index.mdx +++ b/apps/docs/content/docs/orm/reference/errors/index.mdx @@ -3,7 +3,7 @@ title: Prisma Error Reference description: Common Prisma ORM errors and how to troubleshoot them url: /orm/reference/errors metaTitle: Errors -metaDescription: 'Prisma Client, Migrate, Introspection error message reference' +metaDescription: "Prisma Client, Migrate, Introspection error message reference" --- This section provides information about common errors you might encounter when using Prisma ORM and how to resolve them. diff --git a/apps/docs/content/docs/orm/reference/preview-features/client-preview-features.mdx b/apps/docs/content/docs/orm/reference/preview-features/client-preview-features.mdx index 3542c423a0..a9d198e24a 100644 --- a/apps/docs/content/docs/orm/reference/preview-features/client-preview-features.mdx +++ b/apps/docs/content/docs/orm/reference/preview-features/client-preview-features.mdx @@ -14,16 +14,16 @@ For more information, see [ORM releases and maturity levels](/orm/more/releases) The following [Preview](/orm/more/releases#preview) feature flags are available for Prisma Client and Prisma schema: -| Feature | Released into Preview | Feedback issue | -| ----------------------------------------------------------------------- | :------------------------------------------------------------- | :-------------------------------------------------------------------: | -| [`views`](/orm/prisma-schema/data-model/views) | [4.9.0](https://github.com/prisma/prisma/releases/tag/4.9.0) | [Submit feedback](https://github.com/prisma/prisma/issues/17335) | -| `relationJoins` | [5.7.0](https://github.com/prisma/prisma/releases/tag/5.7.0) | [Submit feedback](https://github.com/prisma/prisma/discussions/22288) | -| `nativeDistinct` | [5.7.0](https://github.com/prisma/prisma/releases/tag/5.7.0) | [Submit feedback](https://github.com/prisma/prisma/discussions/22287) | -| `typedSql` | [5.19.0](https://github.com/prisma/prisma/releases/tag/5.19.0) | [Submit feedback](https://github.com/prisma/prisma/discussions/25106) | -| `strictUndefinedChecks` | [5.20.0](https://github.com/prisma/prisma/releases/tag/5.20.0) | [Submit feedback](https://github.com/prisma/prisma/discussions/25271) | +| Feature | Released into Preview | Feedback issue | +| -------------------------------------------------------------------------- | :------------------------------------------------------------- | :-------------------------------------------------------------------: | +| [`views`](/orm/prisma-schema/data-model/views) | [4.9.0](https://github.com/prisma/prisma/releases/tag/4.9.0) | [Submit feedback](https://github.com/prisma/prisma/issues/17335) | +| `relationJoins` | [5.7.0](https://github.com/prisma/prisma/releases/tag/5.7.0) | [Submit feedback](https://github.com/prisma/prisma/discussions/22288) | +| `nativeDistinct` | [5.7.0](https://github.com/prisma/prisma/releases/tag/5.7.0) | [Submit feedback](https://github.com/prisma/prisma/discussions/22287) | +| `typedSql` | [5.19.0](https://github.com/prisma/prisma/releases/tag/5.19.0) | [Submit feedback](https://github.com/prisma/prisma/discussions/25106) | +| `strictUndefinedChecks` | [5.20.0](https://github.com/prisma/prisma/releases/tag/5.20.0) | [Submit feedback](https://github.com/prisma/prisma/discussions/25271) | | [`fullTextSearchPostgres`](/v6/orm/prisma-client/queries/full-text-search) | [6.0.0](https://github.com/prisma/prisma/releases/tag/6.0.0) | [Submit feedback](https://github.com/prisma/prisma/issues/25773) | -| `shardKeys` | [6.10.0](https://pris.ly/release/6.10.0) | [Submit feedback](https://github.com/prisma/prisma/issues/) | -| [`partialIndexes`](/orm/prisma-schema/data-model/indexes) | [7.4.0](https://pris.ly/release/7.4.0) | [Submit feedback](https://github.com/prisma/prisma/issues/6974) | +| `shardKeys` | [6.10.0](https://pris.ly/release/6.10.0) | [Submit feedback](https://github.com/prisma/prisma/issues/) | +| [`partialIndexes`](/orm/prisma-schema/data-model/indexes) | [7.4.0](https://pris.ly/release/7.4.0) | [Submit feedback](https://github.com/prisma/prisma/issues/6974) | To enable a Preview feature, [add the feature flag to the `generator` block](#enabling-a-prisma-client-preview-feature) in your `schema.prisma` file. [Share your feedback on all Preview features on GitHub](https://github.com/prisma/prisma/issues/3108). @@ -65,7 +65,7 @@ In the list below, you can find a history of Prisma Client and Prisma schema fea | [`clientExtensions`](/orm/prisma-client/client-extensions) | [4.7.0](https://github.com/prisma/prisma/releases/tag/4.7.0) | [4.16.0](https://github.com/prisma/prisma/releases/tag/4.16.0) | | [`filteredRelationCount`](/orm/prisma-client/queries/aggregation-grouping-summarizing#filter-the-relation-count) | [4.3.0](https://github.com/prisma/prisma/releases/tag/4.3.0) | [4.16.0](https://github.com/prisma/prisma/releases/tag/4.16.0) | | [`tracing`](/orm/prisma-client/observability-and-logging/opentelemetry-tracing) | [4.2.0](https://github.com/prisma/prisma/releases/tag/4.2.0) | [6.1.0](https://github.com/prisma/prisma/releases/tag/6.1.0) | -| [`orderByNulls`](/v6/orm/prisma-client/queries/filtering-and-sorting#sort-with-null-records-first-or-last) | [4.1.0](https://github.com/prisma/prisma/releases/tag/4.1.0) | [4.16.0](https://github.com/prisma/prisma/releases/tag/4.16.0) | +| [`orderByNulls`](/v6/orm/prisma-client/queries/filtering-and-sorting#sort-with-null-records-first-or-last) | [4.1.0](https://github.com/prisma/prisma/releases/tag/4.1.0) | [4.16.0](https://github.com/prisma/prisma/releases/tag/4.16.0) | | [`referentialIntegrity`](/orm/prisma-schema/data-model/relations/relation-mode) | [3.1.1](https://github.com/prisma/prisma/releases/tag/3.1.1) | [4.7.0](https://github.com/prisma/prisma/releases/tag/4.7.0) | | [`interactiveTransactions`](/orm/prisma-client/queries/transactions#interactive-transactions) | [2.29.0](https://github.com/prisma/prisma/releases/tag/2.29.0) | [4.7.0](https://github.com/prisma/prisma/releases/tag/4.7.0)
with Prisma Accelerate [5.1.1](https://github.com/prisma/prisma/releases/tag/5.1.1) | | [`extendedIndexes`](/orm/prisma-schema/data-model/indexes) | [3.5.0](https://github.com/prisma/prisma/releases/tag/3.5.0) | [4.0.0](https://github.com/prisma/prisma/releases/tag/4.0.0) | @@ -77,7 +77,7 @@ In the list below, you can find a history of Prisma Client and Prisma schema fea | [`namedConstraints`](/orm/prisma-schema/data-model/database-mapping#constraint-and-index-names) | [2.29.0](https://github.com/prisma/prisma/releases/tag/2.29.0) | [3.0.1](https://github.com/prisma/prisma/releases/tag/3.0.1) | | [`referentialActions`](/orm/prisma-schema/data-model/relations/referential-actions) | [2.26.0](https://github.com/prisma/prisma/releases/tag/2.26.0) | [3.0.1](https://github.com/prisma/prisma/releases/tag/3.0.1) | | [`orderByAggregateGroup`](/orm/prisma-client/queries/aggregation-grouping-summarizing#order-by-aggregate-group) | [2.21.0](https://github.com/prisma/prisma/releases/tag/2.21.0) | [3.0.1](https://github.com/prisma/prisma/releases/tag/3.0.1) | -| [`orderByRelation`](/v6/orm/prisma-client/queries/filtering-and-sorting#sort-by-relation) | [2.16.0](https://github.com/prisma/prisma/releases/tag/2.16.0)
aggregates in [2.19.0](https://github.com/prisma/prisma/releases/tag/2.19.0) | [3.0.1](https://github.com/prisma/prisma/releases/tag/3.0.1) | +| [`orderByRelation`](/v6/orm/prisma-client/queries/filtering-and-sorting#sort-by-relation) | [2.16.0](https://github.com/prisma/prisma/releases/tag/2.16.0)
aggregates in [2.19.0](https://github.com/prisma/prisma/releases/tag/2.19.0) | [3.0.1](https://github.com/prisma/prisma/releases/tag/3.0.1) | | [`selectRelationCount`](/orm/prisma-client/queries/aggregation-grouping-summarizing#count-relations) | [2.20.0](https://github.com/prisma/prisma/releases/tag/2.20.0) | [3.0.1](https://github.com/prisma/prisma/releases/tag/3.0.1) | | `napi` | [2.20.0](https://github.com/prisma/prisma/releases/tag/2.20.0) | [3.0.1](https://github.com/prisma/prisma/releases/tag/3.0.1) | | [`groupBy`](/orm/reference/prisma-client-reference#groupby) | [2.14.0](https://github.com/prisma/prisma/releases/tag/2.14.0) | [2.20.0](https://github.com/prisma/prisma/releases/tag/2.20.0) | @@ -87,6 +87,6 @@ In the list below, you can find a history of Prisma Client and Prisma schema fea | [`transactionApi`](/orm/prisma-client/queries/transactions#the-transaction-api) | [2.1.0](https://github.com/prisma/prisma/releases/tag/2.1.0) | [2.11.0](https://github.com/prisma/prisma/releases/tag/2.11.0) | | [`connectOrCreate`](/orm/reference/prisma-client-reference#connectorcreate) | [2.1.0](https://github.com/prisma/prisma/releases/tag/2.1.0) | [2.11.0](https://github.com/prisma/prisma/releases/tag/2.11.0) | | [`atomicNumberOperations`](/orm/reference/prisma-client-reference#atomic-number-operations) | [2.6.0](https://github.com/prisma/prisma/releases/tag/2.6.0) | [2.10.0](https://github.com/prisma/prisma/releases/tag/2.10.0) | -| [`insensitiveFilters` (PostgreSQL)](/v6/orm/prisma-client/queries/filtering-and-sorting#case-insensitive-filtering) | [2.5.0](https://github.com/prisma/prisma/releases/tag/2.5.0) | [2.8.0](https://github.com/prisma/prisma/releases/tag/2.8.0) | +| [`insensitiveFilters` (PostgreSQL)](/v6/orm/prisma-client/queries/filtering-and-sorting#case-insensitive-filtering) | [2.5.0](https://github.com/prisma/prisma/releases/tag/2.5.0) | [2.8.0](https://github.com/prisma/prisma/releases/tag/2.8.0) | | [`aggregateApi`](/orm/prisma-client/queries/aggregation-grouping-summarizing#aggregate) | [2.2.0](https://github.com/prisma/prisma/releases/tag/2.2.0) | [2.5.0](https://github.com/prisma/prisma/releases/tag/2.5.0) | | [`distinct`](/orm/reference/prisma-client-reference#distinct) | [2.3.0](https://github.com/prisma/prisma/releases/tag/2.3.0) | [2.5.0](https://github.com/prisma/prisma/releases/tag/2.5.0) | diff --git a/apps/docs/content/docs/orm/reference/prisma-cli-reference.mdx b/apps/docs/content/docs/orm/reference/prisma-cli-reference.mdx index 36420d2f95..761b1bf074 100644 --- a/apps/docs/content/docs/orm/reference/prisma-cli-reference.mdx +++ b/apps/docs/content/docs/orm/reference/prisma-cli-reference.mdx @@ -1,9 +1,9 @@ --- title: Prisma CLI reference -description: 'This page gives an overview of all available Prisma CLI commands, explains their options and shows numerous usage examples' +description: "This page gives an overview of all available Prisma CLI commands, explains their options and shows numerous usage examples" url: /orm/reference/prisma-cli-reference metaTitle: Prisma CLI reference -metaDescription: 'This page gives an overview of all available Prisma CLI commands, explains their options and shows numerous usage examples.' +metaDescription: "This page gives an overview of all available Prisma CLI commands, explains their options and shows numerous usage examples." --- This document describes the Prisma CLI commands, arguments, and options. @@ -91,7 +91,7 @@ By default, the project sets up a [local Prisma Postgres](/postgres/database/loc #### Arguments | Argument | Required | Description | Default | -| ------------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | +| ------------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | | `--datasource-provider` | No | Specifies the value for the `provider` field in the `datasource` block. Options are `prisma+postgres`, `sqlite`, `postgresql`, `mysql`, `sqlserver`, `mongodb` and `cockroachdb`. | `postgresql` | | `--db` | No | Shorthand syntax for `--datasource-provider prisma+postgres`; creates a new [Prisma Postgres](/postgres) instance. Requires authentication in the [PDP Console](https://console.prisma.io). | | | `--prompt` (or `--vibe`) | No | Scaffolds a Prisma schema based on the prompt and deploys it to a fresh Prisma Postgres instance. Requires authentication in the [PDP Console](https://console.prisma.io). | | @@ -393,14 +393,14 @@ generator client { #### Options -| Option | Required | Description | Default | -| ------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | +| Option | Required | Description | Default | +| ------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | `--data-proxy` | No | The `generate` command will generate Prisma Client for use with [Prisma Accelerate](/accelerate) prior to Prisma 5.0.0. Mutually exclusive with `--accelerate` and `--no-engine`. | | `--accelerate` | No | The `generate` command will generate Prisma Client for use with [Prisma Accelerate](/accelerate). Mutually exclusive with `--data-proxy` and `--no-engine`. Available in Prisma 5.1.0 and later. | | `--no-engine` | No | The `generate` command will generate Prisma Client without an accompanied engine for use with [Prisma Accelerate](/accelerate). Mutually exclusive with `--data-proxy` and `--accelerate`. Available in Prisma ORM 5.2.0 and later. | -| `--no-hints` | No | The `generate` command will generate Prisma Client without usage hints, surveys or info banners being printed to the terminal. Available in Prisma ORM 5.16.0 and later. | -| `--allow-no-models` | No | The `generate` command will generate Prisma Client without generating any models. | -| `--watch` | No | The `generate` command will continue to watch the `schema.prisma` file and re-generate Prisma Client on file changes. | +| `--no-hints` | No | The `generate` command will generate Prisma Client without usage hints, surveys or info banners being printed to the terminal. Available in Prisma ORM 5.16.0 and later. | +| `--allow-no-models` | No | The `generate` command will generate Prisma Client without generating any models. | +| `--watch` | No | The `generate` command will continue to watch the `schema.prisma` file and re-generate Prisma Client on file changes. | :::warning @@ -681,14 +681,14 @@ The `dev` command starts a [local Prisma Postgres](/postgres/database/local-deve ### Arguments -| Argument | Required | Description | Default | -| --------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | +| Argument | Required | Description | Default | +| --------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | | `--name` (or `-n`) | No | Enables targeting a specific database instance. [Learn more](/postgres/database/local-development#using-different-local-prisma-postgres-instances). | `default` | -| `--port` (or `-p`) | No | Main port number the local Prisma Postgres HTTP server will listen on. | `51213` | -| `--db-port` (or `-P`) | No | Port number the local Prisma Postgres database server will listen on. | `51214` | -| `--shadow-db-port` | No | Port number the shadow database server will listen on. | `51215` | -| `--detach` (or `-d`) | No | Run the server in the background. | `false` | -| `--debug` | No | Enable debug logging. | `false` | +| `--port` (or `-p`) | No | Main port number the local Prisma Postgres HTTP server will listen on. | `51213` | +| `--db-port` (or `-P`) | No | Port number the local Prisma Postgres database server will listen on. | `51214` | +| `--shadow-db-port` | No | Port number the shadow database server will listen on. | `51215` | +| `--detach` (or `-d`) | No | Run the server in the background. | `false` | +| `--debug` | No | Enable debug logging. | `false` | ### Examples @@ -812,8 +812,8 @@ npx prisma dev rm mydb* # removes all DBs starting with `mydb` #### Arguments -| Argument | Required | Description | Default | -| --------- | -------- | -------------------------------------------------------------------------------------------------------------------- | ------- | +| Argument | Required | Description | Default | +| --------- | -------- | ------------------------------------------------------------------------------------------------------------------ | ------- | | `--force` | No | Stops any running servers before removing them. Without this flag, the command will fail if any server is running. | `false` | :::note @@ -1498,7 +1498,7 @@ One of the following `--from-...` options is required: | `--from-empty` | Assume that the data model you are migrating from is empty | | | `--from-schema` | Path to a Prisma schema file, uses the data model for the diff | | | `--from-migrations` | Path to the Prisma Migrate migrations directory | Not supported in MongoDB | -| `--from-config-datasource` | Use the datasource from the Prisma config file | Prisma v7+ | +| `--from-config-datasource` | Use the datasource from the Prisma config file | Prisma v7+ | One of the following `--to-...` options is required: @@ -1507,7 +1507,7 @@ One of the following `--to-...` options is required: | `--to-empty` | Assume that the data model you are migrating to is empty | | | `--to-schema` | Path to a Prisma schema file, uses the data model for the diff | | | `--to-migrations` | Path to the Prisma Migrate migrations directory | Not supported in MongoDB | -| `--to-config-datasource` | Use the datasource from the Prisma config file | Prisma v7+ | +| `--to-config-datasource` | Use the datasource from the Prisma config file | Prisma v7+ | Other options: diff --git a/apps/docs/content/docs/orm/reference/prisma-client-reference.mdx b/apps/docs/content/docs/orm/reference/prisma-client-reference.mdx index 0552094498..3887a114d9 100644 --- a/apps/docs/content/docs/orm/reference/prisma-client-reference.mdx +++ b/apps/docs/content/docs/orm/reference/prisma-client-reference.mdx @@ -377,10 +377,10 @@ Sets default [transaction options](/orm/prisma-client/queries/transactions#trans #### Options -| Option | Description | -| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `maxWait` | The maximum amount of time Prisma Client will wait to acquire a transaction from the database. The default value is 2 seconds. | -| `timeout` | The maximum amount of time the interactive transaction can run before being canceled and rolled back. The default value is 5 seconds. | +| Option | Description | +| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `maxWait` | The maximum amount of time Prisma Client will wait to acquire a transaction from the database. The default value is 2 seconds. | +| `timeout` | The maximum amount of time the interactive transaction can run before being canceled and rolled back. The default value is 5 seconds. | | `isolationLevel` | Sets the [transaction isolation level](/orm/prisma-client/queries/transactions#transaction-isolation-level). By default this is set to the value currently configured in your database. The available levels can vary depending on the database you use. | #### Example @@ -414,13 +414,13 @@ Use model queries to perform CRUD operations on your models. See also: [CRUD](/o #### Options -| Name | Example type (`User`) | Required | Description | -| ---------------------- | ------------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `where` | `UserWhereUniqueInput` | **Yes** | Wraps all fields of a model so that a record can be selected ([learn more](#filter-on-non-unique-fields-with-userwhereuniqueinput)). | -| `select` | `XOR` | No | [Specifies which properties to include](/v6/orm/prisma-client/queries/select-fields) on the returned object. | -| `include` | `XOR` | No | [Specifies which relations should be eagerly loaded](/orm/prisma-client/queries/relation-queries) on the returned object. | -| `omit` | `XOR` | No | Specifies which properties to exclude on the returned object. Excludes specified fields from the result | -| `relationLoadStrategy` | `'join'` or `'query'` | No | **Default: `join`**. [Load strategy](/orm/prisma-client/queries/relation-queries#relation-load-strategies-preview) for relations. Requires `relationJoins` preview feature. | +| Name | Example type (`User`) | Required | Description | +| ---------------------- | ------------------------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `where` | `UserWhereUniqueInput` | **Yes** | Wraps all fields of a model so that a record can be selected ([learn more](#filter-on-non-unique-fields-with-userwhereuniqueinput)). | +| `select` | `XOR` | No | [Specifies which properties to include](/v6/orm/prisma-client/queries/select-fields) on the returned object. | +| `include` | `XOR` | No | [Specifies which relations should be eagerly loaded](/orm/prisma-client/queries/relation-queries) on the returned object. | +| `omit` | `XOR` | No | Specifies which properties to exclude on the returned object. Excludes specified fields from the result | +| `relationLoadStrategy` | `'join'` or `'query'` | No | **Default: `join`**. [Load strategy](/orm/prisma-client/queries/relation-queries#relation-load-strategies-preview) for relations. Requires `relationJoins` preview feature. | #### Return type @@ -545,7 +545,7 @@ await prisma.user.findUniqueOrThrow({ | Name | Example type (`User`) | Required | Description | | ---------------------- | ----------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `select` | `XOR` | No | [Specifies which properties to include](/v6/orm/prisma-client/queries/select-fields) on the returned object. | +| `select` | `XOR` | No | [Specifies which properties to include](/v6/orm/prisma-client/queries/select-fields) on the returned object. | | `include` | `XOR` | No | [Specifies which relations should be eagerly loaded](/orm/prisma-client/queries/relation-queries) on the returned object. | | `omit` | `XOR` | No | Specifies which properties to exclude on the returned object. Excludes specified fields from the result. | | `relationLoadStrategy` | `'join'` or `'query'` | No | **Default: `join`**. [Load strategy](/orm/prisma-client/queries/relation-queries#relation-load-strategies-preview) for relations. Requires `relationJoins` preview feature. | @@ -637,7 +637,7 @@ main(); | Name | Type | Required | Description | | ---------------------- | ------------------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `select` | `XOR` | No | [Specifies which properties to include](/v6/orm/prisma-client/queries/select-fields) on the returned object. | +| `select` | `XOR` | No | [Specifies which properties to include](/v6/orm/prisma-client/queries/select-fields) on the returned object. | | `include` | `XOR` | No | [Specifies which relations should be eagerly loaded](/orm/prisma-client/queries/relation-queries) on the returned object. | | `omit` | `XOR` | No | Specifies which properties to exclude on the returned object. Excludes specified fields from the result | | `relationLoadStrategy` | `'join'` or `'query'` | No | **Default: `join`**. [Load strategy](/orm/prisma-client/queries/relation-queries#relation-load-strategies-preview) for relations. Requires `relationJoins` preview feature. | @@ -677,7 +677,7 @@ const user = await prisma.user.findMany({ | Name | Type | Required | Description | | ---------------------- | -------------------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `data` | `XOR`UserUncheckedCreateInput>` | **Yes** | Wraps all the model fields in a type so that they can be provided when creating new records. It also includes relation fields which lets you perform (transactional) nested inserts. Fields that are marked as optional or have default values in the datamodel are optional. | -| [`select`](#select) | `XOR` | No | [Specifies which properties to include](/v6/orm/prisma-client/queries/select-fields) on the returned object. | +| [`select`](#select) | `XOR` | No | [Specifies which properties to include](/v6/orm/prisma-client/queries/select-fields) on the returned object. | | [`include`](#include) | `XOR` | No | [Specifies which relations should be eagerly loaded](/orm/prisma-client/queries/relation-queries) on the returned object. | | [`omit`](#omit) | `XOR` | No | Specifies which properties to exclude on the returned object. Excludes specified fields from the result | | `relationLoadStrategy` | `'join'` or `'query'` | No | **Default: `join`**. [Load strategy](/orm/prisma-client/queries/relation-queries#relation-load-strategies-preview) for relations. Requires `relationJoins` preview feature. | @@ -768,14 +768,14 @@ prisma:query COMMIT #### Options -| Name | Type | Required | Description | -| ---------------------- | ------------------------------------------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `data` | `XOR`UserUncheckedUpdateInput>` | **Yes** | Wraps all the fields of the model so that they can be provided when updating an existing record. Fields that are marked as optional or have default values in the datamodel are optional. | -| `where` | `UserWhereUniqueInput` | **Yes** | Wraps all fields of a model so that a record can be selected ([learn more](#filter-on-non-unique-fields-with-userwhereuniqueinput)). | -| [`select`](#select) | `XOR` | No | [Specifies which properties to include](/v6/orm/prisma-client/queries/select-fields) on the returned object. | -| [`include`](#include) | `XOR` | No | [Specifies which relations should be eagerly loaded](/orm/prisma-client/queries/relation-queries) on the returned object. | -| [`omit`](#omit) | `XOR` | No | Specifies which properties to exclude on the returned object. Excludes specified fields from the result. | -| `relationLoadStrategy` | `'join'` or `'query'` | No | **Default: `join`**. [Load strategy](/orm/prisma-client/queries/relation-queries#relation-load-strategies-preview) for relations. Requires `relationJoins` preview feature. | +| Name | Type | Required | Description | +| ---------------------- | ------------------------------------------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data` | `XOR`UserUncheckedUpdateInput>` | **Yes** | Wraps all the fields of the model so that they can be provided when updating an existing record. Fields that are marked as optional or have default values in the datamodel are optional. | +| `where` | `UserWhereUniqueInput` | **Yes** | Wraps all fields of a model so that a record can be selected ([learn more](#filter-on-non-unique-fields-with-userwhereuniqueinput)). | +| [`select`](#select) | `XOR` | No | [Specifies which properties to include](/v6/orm/prisma-client/queries/select-fields) on the returned object. | +| [`include`](#include) | `XOR` | No | [Specifies which relations should be eagerly loaded](/orm/prisma-client/queries/relation-queries) on the returned object. | +| [`omit`](#omit) | `XOR` | No | Specifies which properties to exclude on the returned object. Excludes specified fields from the result. | +| `relationLoadStrategy` | `'join'` or `'query'` | No | **Default: `join`**. [Load strategy](/orm/prisma-client/queries/relation-queries#relation-load-strategies-preview) for relations. Requires `relationJoins` preview feature. | #### Return type @@ -820,8 +820,8 @@ This section covers the usage of the `upsert()` operation. To learn about using | ---------------------- | ------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `create` | `XOR`UserUncheckedCreateInput>` | **Yes** | Wraps all the fields of the model so that they can be provided when creating new records. It also includes relation fields which lets you perform (transactional) nested inserts. Fields that are marked as optional or have default values in the datamodel are optional. | | `update` | `XOR`UserUncheckedUpdateInput>` | **Yes** | Wraps all the fields of the model so that they can be provided when updating an existing record. Fields that are marked as optional or have default values in the datamodel are optional. | -| `where` | `UserWhereUniqueInput` | **Yes** | Wraps all fields of a model so that a record can be selected ([learn more](#filter-on-non-unique-fields-with-userwhereuniqueinput)). | -| [`select`](#select) | `XOR` | No | [Specifies which properties to include](/v6/orm/prisma-client/queries/select-fields) on the returned object. | +| `where` | `UserWhereUniqueInput` | **Yes** | Wraps all fields of a model so that a record can be selected ([learn more](#filter-on-non-unique-fields-with-userwhereuniqueinput)). | +| [`select`](#select) | `XOR` | No | [Specifies which properties to include](/v6/orm/prisma-client/queries/select-fields) on the returned object. | | [`include`](#include) | `XOR` | No | [Specifies which relations should be eagerly loaded](/orm/prisma-client/queries/relation-queries) on the returned object. | | [`omit`](#omit) | `XOR` | No | Specifies which properties to exclude on the returned object. Excludes specified fields from the result | | `relationLoadStrategy` | `'join'` or `'query'` | No | **Default: `join`**. [Load strategy](/orm/prisma-client/queries/relation-queries#relation-load-strategies-preview) for relations. Requires `relationJoins` preview feature. | @@ -1021,13 +1021,13 @@ To delete records that match a certain criteria, use [`deleteMany`](#deletemany) #### Options -| Name | Type | Required | Description | -| ---------------------- | ------------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `where` | `UserWhereUniqueInput` | **Yes** | Wraps all fields of a model so that a record can be selected ([learn more](#filter-on-non-unique-fields-with-userwhereuniqueinput)). | -| [`select`](#select) | `XOR` | No | [Specifies which properties to include](/v6/orm/prisma-client/queries/select-fields) on the returned object. | -| [`include`](#include) | `XOR` | No | [Specifies which relations should be eagerly loaded](/orm/prisma-client/queries/relation-queries) on the returned object. | -| [`omit`](#omit) | `XOR` | No | Specifies which properties to exclude on the returned object. Excludes specified fields from the result | -| `relationLoadStrategy` | `'join'` or `'query'` | No | **Default: `join`**. [Load strategy](/orm/prisma-client/queries/relation-queries#relation-load-strategies-preview) for relations. Requires `relationJoins` preview feature. | +| Name | Type | Required | Description | +| ---------------------- | ------------------------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `where` | `UserWhereUniqueInput` | **Yes** | Wraps all fields of a model so that a record can be selected ([learn more](#filter-on-non-unique-fields-with-userwhereuniqueinput)). | +| [`select`](#select) | `XOR` | No | [Specifies which properties to include](/v6/orm/prisma-client/queries/select-fields) on the returned object. | +| [`include`](#include) | `XOR` | No | [Specifies which relations should be eagerly loaded](/orm/prisma-client/queries/relation-queries) on the returned object. | +| [`omit`](#omit) | `XOR` | No | Specifies which properties to exclude on the returned object. Excludes specified fields from the result | +| `relationLoadStrategy` | `'join'` or `'query'` | No | **Default: `join`**. [Load strategy](/orm/prisma-client/queries/relation-queries#relation-load-strategies-preview) for relations. Requires `relationJoins` preview feature. | #### Return type @@ -1117,7 +1117,7 @@ const users = await prisma.user.createMany({ | Name | Type | Required | Description | | --------------------- | --------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `data` | `Enumerable` | **Yes** | Wraps all the model fields in a type so that they can be provided when creating new records. Fields that are marked as optional or have default values in the datamodel are optional. | -| [`select`](#select) | `XOR` | No | [Specifies which properties to include](/v6/orm/prisma-client/queries/select-fields) on the returned objects. | +| [`select`](#select) | `XOR` | No | [Specifies which properties to include](/v6/orm/prisma-client/queries/select-fields) on the returned objects. | | [`omit`](#omit) | `XOR` | No | Specifies which properties to exclude on the returned objects. Excludes specified fields from the result. Mutually exclusive with `select`. | | [`include`](#include) | `XOR` | No | [Specifies which relations should be eagerly loaded](/orm/prisma-client/queries/relation-queries) on the returned objects. | | `skipDuplicates?` | `boolean` | No | Do not insert records with unique fields or ID fields that already exist. Only supported by databases that support [`ON CONFLICT DO NOTHING`](https://www.postgresql.org/docs/9.5/sql-insert.html#SQL-ON-CONFLICT). This excludes MongoDB and SQLServer | @@ -1424,9 +1424,9 @@ const minMaxAge = await prisma.user.aggregate({ ```json { - _count: { _all: 29 }, - _max: { profileViews: 90 }, - _min: { profileViews: 0 } + "_count": { "_all": 29 }, + "_max": { "profileViews": 90 }, + "_min": { "profileViews": 0 } } ``` @@ -1555,8 +1555,8 @@ const result = await prisma.user.findUnique({ ```json { - name: "Alice", - profileViews: 0 + "name": "Alice", + "profileViews": 0 } ``` @@ -2510,7 +2510,6 @@ generator client { } ``` - See [Preview Features](/orm/reference/preview-features/client-preview-features#preview-features-promoted-to-general-availability) for more details. ## Nested queries @@ -2531,13 +2530,13 @@ A nested `create` query adds a new related record or set of records to a parent ```ts highlight=5;normal const user = await prisma.user.create({ data: { - email: 'alice@prisma.io', + email: "alice@prisma.io", profile: { - create: { bio: 'Hello World' }, + create: { bio: "Hello World" }, }, }, }); -```` +``` ##### Create a new `Profile` record with a new `User` record @@ -4975,11 +4974,7 @@ The following example shows how you can create type-checked input for the `creat ```ts import { Prisma } from "../prisma/generated/client"; -const createUserAndPostInput = ( - name: string, - email: string, - postTitle: string, -) => +const createUserAndPostInput = (name: string, email: string, postTitle: string) => ({ name, email, @@ -5210,10 +5205,15 @@ await prisma.user.update({ where: { id: 1 }, data: { to_one: { - update: { where: { /* WhereInput */ }, data: { field: "updated" } } - } - } -}) + update: { + where: { + /* WhereInput */ + }, + data: { field: "updated" }, + }, + }, + }, +}); ``` ##### Nested upsert example @@ -5224,13 +5224,19 @@ await prisma.user.update({ data: { to_one: { upsert: { - where: { /* WhereInput */ }, - create: { /* CreateInput */ }, - update: { /* UpdateInput */ }, - } - } - } -}) + where: { + /* WhereInput */ + }, + create: { + /* CreateInput */ + }, + update: { + /* UpdateInput */ + }, + }, + }, + }, +}); ``` ##### Nested disconnect example @@ -5240,10 +5246,12 @@ await prisma.user.update({ where: { id: 1 }, data: { to_one: { - disconnect: { /* WhereInput */ } - } - } -}) + disconnect: { + /* WhereInput */ + }, + }, + }, +}); ``` ##### Nested delete example @@ -5253,10 +5261,12 @@ await prisma.user.update({ where: { id: 1 }, data: { to_one: { - delete: { /* WhereInput */ } - } - } -}) + delete: { + /* WhereInput */ + }, + }, + }, +}); ``` ## `PrismaPromise` behavior diff --git a/apps/docs/content/docs/orm/reference/prisma-schema-reference.mdx b/apps/docs/content/docs/orm/reference/prisma-schema-reference.mdx index ad9a4bd4c0..f4105e0ddc 100644 --- a/apps/docs/content/docs/orm/reference/prisma-schema-reference.mdx +++ b/apps/docs/content/docs/orm/reference/prisma-schema-reference.mdx @@ -14,12 +14,12 @@ Defines a [data source](/orm/prisma-schema/overview/data-sources) in the Prisma A `datasource` block accepts the following fields: -| Name | Required | Type | Description | -| -------------- | -------- | ------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | -| `provider` | **Yes** | String (`postgresql`, `mysql`, `sqlite`, `sqlserver`, `mongodb`, `cockroachdb`) | Specifies the database connector to use. | -| `relationMode` | No | String (`foreignKeys`, `prisma`) | Sets whether [referential integrity](/orm/prisma-schema/data-model/relations/relation-mode) is enforced by foreign keys or by Prisma. | -| `schemas` | No | Array of strings | List of database schemas to include ([multi-schema](/orm/prisma-schema/data-model/multi-schema) support, PostgreSQL and SQL Server). | -| `extensions` | No | Array of extension names | [PostgreSQL extensions](/orm/prisma-schema/postgresql-extensions) to enable. | +| Name | Required | Type | Description | +| -------------- | -------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| `provider` | **Yes** | String (`postgresql`, `mysql`, `sqlite`, `sqlserver`, `mongodb`, `cockroachdb`) | Specifies the database connector to use. | +| `relationMode` | No | String (`foreignKeys`, `prisma`) | Sets whether [referential integrity](/orm/prisma-schema/data-model/relations/relation-mode) is enforced by foreign keys or by Prisma. | +| `schemas` | No | Array of strings | List of database schemas to include ([multi-schema](/orm/prisma-schema/data-model/multi-schema) support, PostgreSQL and SQL Server). | +| `extensions` | No | Array of extension names | [PostgreSQL extensions](/orm/prisma-schema/postgresql-extensions) to enable. | Connection URLs (`url`, `directUrl`, `shadowDatabaseUrl`) are configured in [`prisma.config.ts`](/orm/reference/prisma-config-reference#datasourceurl), not in the schema file. @@ -145,14 +145,14 @@ This is the default generator for Prisma ORM 6.x and earlier versions. Learn mor A `generator` block accepts the following fields: -| Name | Required | Type | Description | -| :---------------- | :------- | :--------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `provider` | **Yes** | `prisma-client-js` | Describes which [generator](/orm/prisma-schema/overview/generators) to use. This can point to a file that implements a generator or specify a built-in generator directly. | +| Name | Required | Type | Description | +| :---------------- | :------- | :--------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `provider` | **Yes** | `prisma-client-js` | Describes which [generator](/orm/prisma-schema/overview/generators) to use. This can point to a file that implements a generator or specify a built-in generator directly. | | `output` | No | String (file path) | Determines the location for the generated client, [learn more](/orm/reference/prisma-schema-reference#fields-for-prisma-client-provider). **Default**: `node_modules/.prisma/client` | -| `previewFeatures` | No | List of Enums | Use intellisense to see list of currently available Preview features (`Ctrl+Space` in Visual Studio Code) **Default**: none | -| `engineType` | No | Enum (`library` or `binary`) | Defines the query engine type to download and use. **Default**: `library` | -| `binaryTargets` | No | List of Enums (see below) | Specify the OS on which the Prisma Client will run to ensure compatibility of the query engine. **Default**: `native` | -| `moduleFormat` | No | Enum (`cjs` or `esm`) | Defines the module format of the generated Prisma Client. This field is available only with `prisma-client` generator. | +| `previewFeatures` | No | List of Enums | Use intellisense to see list of currently available Preview features (`Ctrl+Space` in Visual Studio Code) **Default**: none | +| `engineType` | No | Enum (`library` or `binary`) | Defines the query engine type to download and use. **Default**: `library` | +| `binaryTargets` | No | List of Enums (see below) | Specify the OS on which the Prisma Client will run to ensure compatibility of the query engine. **Default**: `native` | +| `moduleFormat` | No | Enum (`cjs` or `esm`) | Defines the module format of the generated Prisma Client. This field is available only with `prisma-client` generator. | :::note[important] @@ -173,9 +173,9 @@ The `prisma-client` generator is the default generator in Prisma ORM 7. A `generator` block accepts the following fields: | Name | Required | Type | Description | -| :----------------------- | :------- | :----------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| :----------------------- | :------- | :----------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `provider` | **Yes** | `prisma-client` | Describes which [generator](/orm/prisma-schema/overview/generators) to use. This can point to a file that implements a generator or specify a built-in generator directly. | -| `output` | **Yes** | String (file path) | Determines the location for the generated client, [learn more](/orm/reference/prisma-schema-reference#fields-for-prisma-client-provider). | +| `output` | **Yes** | String (file path) | Determines the location for the generated client, [learn more](/orm/reference/prisma-schema-reference#fields-for-prisma-client-provider). | | `previewFeatures` | No | List of Enums | Use intellisense to see list of currently available Preview features (`Ctrl+Space` in Visual Studio Code) **Default**: none | | `runtime` | No | Enum (`nodejs`, `deno`, `bun`, `workerd` (alias `cloudflare`), `vercel-edge` (alias `edge-light`), `react-native`) | Target runtime environment. **Default**: `nodejs` | | `moduleFormat` | No | Enum (`esm` or `cjs`) | Determines whether the generated code supports ESM (uses `import`) or CommonJS (uses `require(...)`) modules. We always recommend `esm` unless you have a good reason to use `cjs`. **Default**: Inferred from environment. | @@ -1162,12 +1162,12 @@ Defines a single-field ID on the model. #### Arguments -| Name | Required | Type | Description | -| ----------- | -------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `map` | **No** | `String` | The name of the underlying primary key constraint in the database.

Not supported for MySQL or MongoDB. | +| Name | Required | Type | Description | +| ----------- | -------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `map` | **No** | `String` | The name of the underlying primary key constraint in the database.

Not supported for MySQL or MongoDB. | | `length` | **No** | `number` | Allows you to specify a maximum length for the subpart of the value to be indexed.

MySQL only. | | `sort` | **No** | `String` | Allows you to specify in what order the entries of the ID are stored in the database. The available options are `Asc` and `Desc`.

SQL Server only. | -| `clustered` | **No** | `Boolean` | Defines whether the ID is clustered or non-clustered. Defaults to `true`.

SQL Server only. | +| `clustered` | **No** | `Boolean` | Defines whether the ID is clustered or non-clustered. Defaults to `true`.

SQL Server only. | #### Signature @@ -1401,14 +1401,14 @@ Defines a multi-field ID (composite ID) on the model. #### Arguments -| Name | Required | Type | Description | -| ----------- | -------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `fields` | **Yes** | `FieldReference[]` | A list of field names - for example, `["firstname", "lastname"]` | -| `name` | **No** | `String` | The name that Prisma Client will expose for the argument covering all fields, e.g. `fullName` in `fullName: { firstName: "First", lastName: "Last"}` | -| `map` | **No** | `String` | The name of the underlying primary key constraint in the database.

Not supported for MySQL. | +| Name | Required | Type | Description | +| ----------- | -------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `fields` | **Yes** | `FieldReference[]` | A list of field names - for example, `["firstname", "lastname"]` | +| `name` | **No** | `String` | The name that Prisma Client will expose for the argument covering all fields, e.g. `fullName` in `fullName: { firstName: "First", lastName: "Last"}` | +| `map` | **No** | `String` | The name of the underlying primary key constraint in the database.

Not supported for MySQL. | | `length` | **No** | `number` | Allows you to specify a maximum length for the subpart of the value to be indexed.

MySQL only. | | `sort` | **No** | `String` | Allows you to specify in what order the entries of the ID are stored in the database. The available options are `Asc` and `Desc`.

SQL Server only. | -| `clustered` | **No** | `Boolean` | Defines whether the ID is clustered or non-clustered. Defaults to `true`.

SQL Server only. | +| `clustered` | **No** | `Boolean` | Defines whether the ID is clustered or non-clustered. Defaults to `true`.

SQL Server only. | The name of the `fields` argument on the `@@id` attribute can be omitted: diff --git a/apps/docs/content/docs/postgres/database/backups.mdx b/apps/docs/content/docs/postgres/database/backups.mdx index 1b602aae11..39604a26a0 100644 --- a/apps/docs/content/docs/postgres/database/backups.mdx +++ b/apps/docs/content/docs/postgres/database/backups.mdx @@ -47,6 +47,7 @@ brew install postgresql@17 which pg_dump which pg_restore ``` + ```bash tab="Windows" # Download from the official PostgreSQL website: # https://www.postgresql.org/download/windows/ @@ -56,6 +57,7 @@ which pg_restore where pg_dump where pg_restore ``` + ```bash tab="Linux" sudo apt-get update sudo apt-get install postgresql-client-17 diff --git a/apps/docs/content/docs/postgres/database/connecting-to-your-database.mdx b/apps/docs/content/docs/postgres/database/connecting-to-your-database.mdx index 01546b7c0d..044a174459 100644 --- a/apps/docs/content/docs/postgres/database/connecting-to-your-database.mdx +++ b/apps/docs/content/docs/postgres/database/connecting-to-your-database.mdx @@ -42,12 +42,12 @@ DIRECT_URL="postgres://USER:PASSWORD@db.prisma.io:5432/?sslmode=require" ## Which connection to use -| If you are... | Use | Why | -| ---------------------------------------------------------------- | ---------- | -------------------------------------------------------------------- | -| Running application queries (API handlers, workers, functions) | **Pooled** | Safer under concurrency. Reuses a small set of database connections. | +| If you are... | Use | Why | +| ------------------------------------------------------------------------------------------- | ---------- | -------------------------------------------------------------------- | +| Running application queries (API handlers, workers, functions) | **Pooled** | Safer under concurrency. Reuses a small set of database connections. | | Running migrations, introspection, `pg_dump`, `pg_restore`, Prisma Studio, or admin tooling | **Direct** | These workflows need session continuity or should bypass the pooler. | -| Using `LISTEN/NOTIFY` or session-level `SET` commands | **Direct** | The pooler does not preserve session state across transactions. | -| Running queries or jobs that may exceed 10 minutes | **Direct** | Pooled connections enforce a 10-minute query timeout. | +| Using `LISTEN/NOTIFY` or session-level `SET` commands | **Direct** | The pooler does not preserve session state across transactions. | +| Running queries or jobs that may exceed 10 minutes | **Direct** | Pooled connections enforce a 10-minute query timeout. | If you swap them, the failure modes are asymmetric. Using the pooled string for migrations produces obvious errors (lock failures, prepared statement errors). Using the direct string for application traffic works at low concurrency but silently exhausts connections under production load. See [Connection pooling](/postgres/database/connection-pooling) for details. @@ -114,7 +114,9 @@ if (process.env.NODE_ENV !== "production") { - The serverless driver uses the **direct** connection string but does **not** open a TCP connection to the database. The direct hostname is used for routing only, the transport is HTTP/WebSocket, not PostgreSQL wire protocol. + The serverless driver uses the **direct** connection string but does **not** open a TCP + connection to the database. The direct hostname is used for routing only, the transport is + HTTP/WebSocket, not PostgreSQL wire protocol. diff --git a/apps/docs/content/docs/postgres/database/connection-pooling.mdx b/apps/docs/content/docs/postgres/database/connection-pooling.mdx index e2faed1b23..ee95f5c005 100644 --- a/apps/docs/content/docs/postgres/database/connection-pooling.mdx +++ b/apps/docs/content/docs/postgres/database/connection-pooling.mdx @@ -10,10 +10,10 @@ Prisma Postgres routes application traffic through a tenant-isolated PgBouncer i ## Connection limits -| | Free | Starter | Pro | Business | -| ---------------------- | ---- | ------- | ---- | -------- | -| **Pooled connections** | 50 | 50 | 250 | 500 | -| **Direct connections** | 10 | 10 | 50 | 100 | +| | Free | Starter | Pro | Business | +| ---------------------- | ---- | ------- | --- | -------- | +| **Pooled connections** | 50 | 50 | 250 | 500 | +| **Direct connections** | 10 | 10 | 50 | 100 | These are concurrent connection limits per plan. 5 direct connections are reserved for platform operations (maintenance, monitoring) and are not available to your workload. @@ -68,4 +68,4 @@ Expected behavior in transactional pool mode. The pooler returns the backend con ### Query killed after 10 minutes -Pooled connections enforce a 10-minute query timeout. Break the query into smaller batches, or run it over a direct connection where no timeout is enforced. \ No newline at end of file +Pooled connections enforce a 10-minute query timeout. Break the query into smaller batches, or run it over a direct connection where no timeout is enforced. diff --git a/apps/docs/content/docs/postgres/database/local-development.mdx b/apps/docs/content/docs/postgres/database/local-development.mdx index cdc6adf4fd..58c97e0d50 100644 --- a/apps/docs/content/docs/postgres/database/local-development.mdx +++ b/apps/docs/content/docs/postgres/database/local-development.mdx @@ -248,19 +248,19 @@ try { The `startPrismaDevServer()` function accepts the following options: -| **Argument** | **Required** | **Description** | **Default** | -| ------------------------ | ------------ | ------------------------------------------------------------------------------------------------------------------------------ | ------------- | -| **`name`** | ❌ | Unique identifier for the local Prisma Postgres instance. Use distinct names if running multiple servers in parallel. | `'default'` | -| **`port`** | ❌ | Port for the Prisma engine server. Throws an error if the port is already in use. | `51213` | -| **`databasePort`** | ❌ | Port for the embedded PostgreSQL database. Used for all Prisma ORM connections. | `51214` | -| **`shadowDatabasePort`** | ❌ | Port for the shadow database used during migrations. | `51215` | -| **`persistenceMode`** | ❌ | Defines how data is persisted:
• `'stateless'` — no data is retained between runs
• `'stateful'` — data persists locally | `'stateless'` | -| **`debug`** | ❌ | Whether to enable debug logging. | `false` | -| **`dryRun`** | ❌ | Whether to run the server in dry run mode. | `false` | -| **`databaseConnectTimeoutMillis`** | ❌ | Connection timeout in milliseconds for pending database connections. Starts ticking for every new client that attempts to connect. When exceeded, the pending client connection is evicted and closed. Use with caution, as it may lead to unexpected behavior. Best used with a pool client that retries connections. | `60000` (1 minute) | -| **`databaseIdleTimeoutMillis`** | ❌ | Idle timeout in milliseconds for active database connections. Re-starts ticking after each message received on the active connection. When exceeded, the active client connection is closed, and a pending connection is promoted to active. Use with caution, as it may lead to unexpected disconnections. Best used with a pool client that can handle disconnections gracefully. Set it if you suffer from client hanging indefinitely. | Not applied by default | -| **`shadowDatabaseConnectTimeoutMillis`** | ❌ | Connection timeout in milliseconds for pending shadow database connections. | Defaults to `databaseConnectTimeoutMillis` | -| **`shadowDatabaseIdleTimeoutMillis`** | ❌ | Idle timeout in milliseconds for active shadow database connections. | Defaults to `databaseIdleTimeoutMillis` | +| **Argument** | **Required** | **Description** | **Default** | +| ---------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------ | +| **`name`** | ❌ | Unique identifier for the local Prisma Postgres instance. Use distinct names if running multiple servers in parallel. | `'default'` | +| **`port`** | ❌ | Port for the Prisma engine server. Throws an error if the port is already in use. | `51213` | +| **`databasePort`** | ❌ | Port for the embedded PostgreSQL database. Used for all Prisma ORM connections. | `51214` | +| **`shadowDatabasePort`** | ❌ | Port for the shadow database used during migrations. | `51215` | +| **`persistenceMode`** | ❌ | Defines how data is persisted:
• `'stateless'` — no data is retained between runs
• `'stateful'` — data persists locally | `'stateless'` | +| **`debug`** | ❌ | Whether to enable debug logging. | `false` | +| **`dryRun`** | ❌ | Whether to run the server in dry run mode. | `false` | +| **`databaseConnectTimeoutMillis`** | ❌ | Connection timeout in milliseconds for pending database connections. Starts ticking for every new client that attempts to connect. When exceeded, the pending client connection is evicted and closed. Use with caution, as it may lead to unexpected behavior. Best used with a pool client that retries connections. | `60000` (1 minute) | +| **`databaseIdleTimeoutMillis`** | ❌ | Idle timeout in milliseconds for active database connections. Re-starts ticking after each message received on the active connection. When exceeded, the active client connection is closed, and a pending connection is promoted to active. Use with caution, as it may lead to unexpected disconnections. Best used with a pool client that can handle disconnections gracefully. Set it if you suffer from client hanging indefinitely. | Not applied by default | +| **`shadowDatabaseConnectTimeoutMillis`** | ❌ | Connection timeout in milliseconds for pending shadow database connections. | Defaults to `databaseConnectTimeoutMillis` | +| **`shadowDatabaseIdleTimeoutMillis`** | ❌ | Idle timeout in milliseconds for active shadow database connections. | Defaults to `databaseIdleTimeoutMillis` | :::tip diff --git a/apps/docs/content/docs/postgres/database/query-insights.mdx b/apps/docs/content/docs/postgres/database/query-insights.mdx index cf3eb61672..2360b31c2b 100644 --- a/apps/docs/content/docs/postgres/database/query-insights.mdx +++ b/apps/docs/content/docs/postgres/database/query-insights.mdx @@ -28,4 +28,4 @@ Query Insights is included with Prisma Postgres at no extra cost. :::info Query Insights has its own dedicated section with full documentation, examples, and setup instructions. See [Query Insights](/query-insights). -::: \ No newline at end of file +::: diff --git a/apps/docs/content/docs/postgres/database/serverless-driver.mdx b/apps/docs/content/docs/postgres/database/serverless-driver.mdx index c6e65be8a8..d7fd6418f6 100644 --- a/apps/docs/content/docs/postgres/database/serverless-driver.mdx +++ b/apps/docs/content/docs/postgres/database/serverless-driver.mdx @@ -4,7 +4,7 @@ description: Connect to Prisma Postgres from serverless and edge environments badge: early-access url: /postgres/database/serverless-driver metaTitle: Serverless driver -metaDescription: 'A lightweight PostgreSQL driver for Prisma Postgres optimized for serverless and edge environments with HTTP/WebSocket support, result streaming, and minimal memory footprint.' +metaDescription: "A lightweight PostgreSQL driver for Prisma Postgres optimized for serverless and edge environments with HTTP/WebSocket support, result streaming, and minimal memory footprint." --- The Prisma Postgres serverless driver ([`@prisma/ppg`](https://www.npmjs.com/package/@prisma/ppg)) is a lightweight client for connecting to Prisma Postgres using raw SQL. It uses HTTP and WebSocket protocols instead of traditional TCP connections, enabling database access in constrained environments where native PostgreSQL drivers cannot run. diff --git a/apps/docs/content/docs/postgres/error-reference.mdx b/apps/docs/content/docs/postgres/error-reference.mdx index 53073d6bdd..2eea5cba3e 100644 --- a/apps/docs/content/docs/postgres/error-reference.mdx +++ b/apps/docs/content/docs/postgres/error-reference.mdx @@ -2,7 +2,7 @@ title: Error reference description: Error reference documentation for Prisma Postgres url: /postgres/error-reference -metaTitle: 'Prisma Postgres: Error Reference' +metaTitle: "Prisma Postgres: Error Reference" metaDescription: Error reference documentation for Prisma Postgres. --- diff --git a/apps/docs/content/docs/postgres/faq.mdx b/apps/docs/content/docs/postgres/faq.mdx index d939f98d4b..4f8941ea02 100644 --- a/apps/docs/content/docs/postgres/faq.mdx +++ b/apps/docs/content/docs/postgres/faq.mdx @@ -14,13 +14,13 @@ Common questions about how Prisma Postgres works, how queries are billed, and ho Yes, you can use Prisma Postgres with any database library or tool via a [direct connection](/postgres/database/connection-pooling). -You can find examples of using Prisma Postgres with various ORMs below: +You can find examples of using Prisma Postgres with various ORMs below: + - [Prisma ORM](https://github.com/prisma/prisma-examples/tree/latest/databases/prisma-postgres) - [Drizzle](https://github.com/prisma/prisma-examples/tree/latest/databases/drizzle-prisma-postgres) - [Kysely](https://github.com/prisma/prisma-examples/tree/latest/databases/kysely-prisma-postgres) - [TypeORM](https://github.com/prisma/prisma-examples/tree/latest/databases/typeorm-prisma-postgres) - ### How do I switch from GitHub login to email and password login? If you previously signed up using GitHub and want to switch to email and password login, follow these steps: @@ -259,7 +259,6 @@ Under the hood, Prisma Postgres's cache layer uses Cloudflare, which uses [Anyca You can invalidate the cache on-demand via the [`$accelerate.invalidate` API](/accelerate/reference/api-reference#accelerateinvalidate) if you're on a [paid plan](https://www.prisma.io/pricing#accelerate), or you can invalidate your entire cache, on a project level, a maximum of five times a day. This limit is set based on [your plan](https://www.prisma.io/pricing). You can manage this via the Accelerate configuration page. - ### How is Prisma Postgres's caching layer different from other caching tools, such as Redis? The caching layer of Prisma Postgres: diff --git a/apps/docs/content/docs/postgres/iac/pulumi.mdx b/apps/docs/content/docs/postgres/iac/pulumi.mdx index eab4edd4c8..d70ed22635 100644 --- a/apps/docs/content/docs/postgres/iac/pulumi.mdx +++ b/apps/docs/content/docs/postgres/iac/pulumi.mdx @@ -108,9 +108,11 @@ const connection = new prismaPostgres.Connection("connection", { name: "api-key", }); -const availableRegions = prismaPostgres.getRegionsOutput().regions.apply((regions) => - regions.filter((r) => r.status === "available").map((r) => `${r.id} (${r.name})`) -); +const availableRegions = prismaPostgres + .getRegionsOutput() + .regions.apply((regions) => + regions.filter((r) => r.status === "available").map((r) => `${r.id} (${r.name})`), + ); export const projectId = project.id; export const databaseId = database.id; diff --git a/apps/docs/content/docs/postgres/index.mdx b/apps/docs/content/docs/postgres/index.mdx index dbfdbb4397..85db6a2c87 100644 --- a/apps/docs/content/docs/postgres/index.mdx +++ b/apps/docs/content/docs/postgres/index.mdx @@ -1,6 +1,6 @@ --- title: Prisma Postgres -description: 'Connect to Prisma Postgres from Prisma ORM, serverless runtimes, and PostgreSQL clients.' +description: "Connect to Prisma Postgres from Prisma ORM, serverless runtimes, and PostgreSQL clients." url: /postgres metaTitle: Overview | Prisma Postgres metaDescription: Learn everything you need to know about Prisma Postgres. @@ -19,7 +19,10 @@ New to Prisma Postgres? Start here. Create a temporary Prisma Postgres database in one command. - + Set up Prisma ORM and connect it to Prisma Postgres. diff --git a/apps/docs/content/docs/postgres/integrations/raycast.mdx b/apps/docs/content/docs/postgres/integrations/raycast.mdx index ff055dd5a0..d1c671efdb 100644 --- a/apps/docs/content/docs/postgres/integrations/raycast.mdx +++ b/apps/docs/content/docs/postgres/integrations/raycast.mdx @@ -38,6 +38,7 @@ Instantly create a new Prisma Postgres database with zero configuration: 3. Press Enter to create You'll receive: + - A connection string for immediate use - A direct URL for connecting to your database - A claim URL to make your database permanent diff --git a/apps/docs/content/docs/query-insights/index.mdx b/apps/docs/content/docs/query-insights/index.mdx index 96ffe26ecc..a328c2d16b 100644 --- a/apps/docs/content/docs/query-insights/index.mdx +++ b/apps/docs/content/docs/query-insights/index.mdx @@ -92,8 +92,7 @@ const adapter = new PrismaPg({ export const prisma = new PrismaClient({ adapter: adapter, comments: [prismaQueryInsights()], -}) - +}); ``` This adds SQL comment annotations to queries so Query Insights can map SQL statements back to the Prisma calls that generated them. It is built on top of the [SQL comments](/orm/prisma-client/observability-and-logging/sql-comments) feature in Prisma Client. @@ -106,14 +105,14 @@ Query Insights is included with Prisma Postgres at no extra cost. You can try it Query Insights is most useful when it connects a database symptom to a concrete code change. -| Issue | What you might see | Typical fix | -| --- | --- | --- | -| N+1 queries | High query count for one request | Use nested reads, batching, or joins | -| Missing indexes | High reads relative to rows returned | Add the right index for the filter pattern | -| Over-fetching | Wide rows or large payloads | Use `select` to fetch fewer fields | -| Offset pagination | Reads grow on deeper pages | Switch to cursor pagination | -| Large nested reads | High reads and large payloads | Limit fields, limit depth, or split queries | -| Repeated queries | The same statement shape runs often | Cache or reuse results when appropriate | +| Issue | What you might see | Typical fix | +| ------------------ | ------------------------------------ | ------------------------------------------- | +| N+1 queries | High query count for one request | Use nested reads, batching, or joins | +| Missing indexes | High reads relative to rows returned | Add the right index for the filter pattern | +| Over-fetching | Wide rows or large payloads | Use `select` to fetch fewer fields | +| Offset pagination | Reads grow on deeper pages | Switch to cursor pagination | +| Large nested reads | High reads and large payloads | Limit fields, limit depth, or split queries | +| Repeated queries | The same statement shape runs often | Cache or reuse results when appropriate | ## How to use it @@ -137,6 +136,7 @@ In most cases, the next change falls into one of these buckets: ## Example A common example is an N+1 pattern: + ```ts const users = await prisma.user.findMany({ select: { id: true, name: true, email: true }, @@ -158,6 +158,7 @@ Query Insights would typically show: - More reads and latency than the route should need In this case, the likely fix is to load the related posts in one nested read: + ```ts const usersWithPosts = await prisma.user.findMany({ select: { diff --git a/apps/docs/content/docs/query-insights/meta.json b/apps/docs/content/docs/query-insights/meta.json index 6fc52f3e97..4e2508a762 100644 --- a/apps/docs/content/docs/query-insights/meta.json +++ b/apps/docs/content/docs/query-insights/meta.json @@ -1,8 +1,5 @@ { "title": "Query Insights", "root": true, - "pages": [ - "---Introduction---", - "index" - ] + "pages": ["---Introduction---", "index"] } diff --git a/apps/docs/content/docs/studio/getting-started.mdx b/apps/docs/content/docs/studio/getting-started.mdx index 4f779a8ede..492596e8c8 100644 --- a/apps/docs/content/docs/studio/getting-started.mdx +++ b/apps/docs/content/docs/studio/getting-started.mdx @@ -2,8 +2,8 @@ title: Getting Started description: Learn how to set up and use Prisma Studio to manage your database url: /studio/getting-started -metaTitle: 'Get started with Prisma Studio' -metaDescription: 'Learn how to install Prisma Studio, connect to your database, browse and edit records, and export to CSV or JSON. The visual database browser for Prisma projects.' +metaTitle: "Get started with Prisma Studio" +metaDescription: "Learn how to install Prisma Studio, connect to your database, browse and edit records, and export to CSV or JSON. The visual database browser for Prisma projects." --- ## Installation diff --git a/apps/docs/content/docs/studio/index.mdx b/apps/docs/content/docs/studio/index.mdx index 574bc23c69..6e872da986 100644 --- a/apps/docs/content/docs/studio/index.mdx +++ b/apps/docs/content/docs/studio/index.mdx @@ -3,7 +3,7 @@ title: Prisma Studio description: A visual database editor for viewing and managing your data with Prisma url: /studio metaTitle: Prisma Studio for Prisma Postgres -metaDescription: 'Learn about the various ways of using Prisma Studio, from running locally, to using it in VS Code to embedding it in your own application.' +metaDescription: "Learn about the various ways of using Prisma Studio, from running locally, to using it in VS Code to embedding it in your own application." --- [Prisma Studio](https://www.prisma.io/studio) works with or without Prisma ORM and supports the following workflows: diff --git a/apps/docs/cspell.json b/apps/docs/cspell.json index 6d46d3a553..4e1f3e1a4a 100644 --- a/apps/docs/cspell.json +++ b/apps/docs/cspell.json @@ -345,8 +345,6 @@ "pattern": "/videoId=\"[A-Za-z0-9_-]{6,}\"/g" } ], - "ignoreRegExpList": [ - "mdxVideoIdAttribute" - ], + "ignoreRegExpList": ["mdxVideoIdAttribute"], "ignorePaths": [] } diff --git a/apps/docs/next.config.mjs b/apps/docs/next.config.mjs index 511c2aea02..cbaa60d89f 100644 --- a/apps/docs/next.config.mjs +++ b/apps/docs/next.config.mjs @@ -199,9 +199,7 @@ const securityHeaders = [ }, ]; -const allowedDevOrigins = ( - process.env.ALLOWED_DEV_ORIGINS ?? "localhost,127.0.0.1,192.168.1.48" -) +const allowedDevOrigins = (process.env.ALLOWED_DEV_ORIGINS ?? "localhost,127.0.0.1,192.168.1.48") .split(",") .map((origin) => origin.trim()) .filter(Boolean); diff --git a/apps/docs/scripts/add-url-frontmatter.ts b/apps/docs/scripts/add-url-frontmatter.ts index 272a0afe2d..ef1149e871 100644 --- a/apps/docs/scripts/add-url-frontmatter.ts +++ b/apps/docs/scripts/add-url-frontmatter.ts @@ -12,10 +12,7 @@ import path from "node:path"; import matter from "gray-matter"; const [dirArg, baseArg] = process.argv.slice(2); -const DOCS_DIR = path.join( - process.cwd(), - dirArg ?? "content/docs" -); +const DOCS_DIR = path.join(process.cwd(), dirArg ?? "content/docs"); const URL_BASE = (baseArg ?? "").replace(/\/$/, "") || ""; // "" for /, "/v6" for /v6 function getUrlFromRelativePath(relativePath: string): string { @@ -36,7 +33,7 @@ function getUrlFromRelativePath(relativePath: string): string { function walkMdxFiles( dir: string, relativeTo: string, - files: { absolute: string; relative: string }[] = [] + files: { absolute: string; relative: string }[] = [], ): { absolute: string; relative: string }[] { for (const name of fs.readdirSync(dir, { withFileTypes: true })) { const absolute = path.join(dir, name.name); diff --git a/apps/docs/scripts/fetch-openapi.ts b/apps/docs/scripts/fetch-openapi.ts index af1b2cd65f..1ba16780e6 100644 --- a/apps/docs/scripts/fetch-openapi.ts +++ b/apps/docs/scripts/fetch-openapi.ts @@ -1,11 +1,7 @@ import { writeFile, mkdir } from "node:fs/promises"; import { join } from "node:path"; -async function fetchWithRetry( - url: string, - retries = 3, - timeout = 30000, -): Promise { +async function fetchWithRetry(url: string, retries = 3, timeout = 30000): Promise { for (let i = 0; i < retries; i++) { try { const controller = new AbortController(); @@ -33,10 +29,7 @@ async function fetchWithRetry( // Exponential backoff const delay = Math.min(1000 * Math.pow(2, i), 10000); - console.warn( - `Fetch attempt ${i + 1} failed, retrying in ${delay}ms...`, - error, - ); + console.warn(`Fetch attempt ${i + 1} failed, retrying in ${delay}ms...`, error); await new Promise((resolve) => setTimeout(resolve, delay)); } } diff --git a/apps/docs/scripts/generate-docs.ts b/apps/docs/scripts/generate-docs.ts index 0751c315f0..1c7e61f942 100644 --- a/apps/docs/scripts/generate-docs.ts +++ b/apps/docs/scripts/generate-docs.ts @@ -2,10 +2,7 @@ import { generateFiles } from "fumadocs-openapi"; import matter from "gray-matter"; import { openapi } from "@/lib/openapi"; -function withDescriptionFirst( - data: Record, - description: string, -) { +function withDescriptionFirst(data: Record, description: string) { const { title, description: _description, ...rest } = data; return { @@ -46,12 +43,15 @@ void generateFiles({ return output.item.name; }, beforeWrite(files) { - const operationByFilePath = new Map(); + const operationByFilePath = new Map< + string, + { + path: string; + method: string; + title: string; + description?: string; + } + >(); for (const entries of Object.values(this.generatedEntries)) { for (const entry of entries) { @@ -136,13 +136,9 @@ void generateFiles({ if (!changed) continue; - file.content = matter.stringify( - parsed.content, - withDescriptionFirst(data, description), - { + file.content = matter.stringify(parsed.content, withDescriptionFirst(data, description), { lineWidth: -1, - } as Parameters[2], - ); + } as Parameters[2]); } }, }); diff --git a/apps/docs/scripts/lint-external-links.ts b/apps/docs/scripts/lint-external-links.ts index d77b277dbb..1f3a61c92d 100644 --- a/apps/docs/scripts/lint-external-links.ts +++ b/apps/docs/scripts/lint-external-links.ts @@ -36,8 +36,7 @@ const HTML_ANCHOR_REGEX = /]*\bhref=(["'])(.*?)\1/gi; const BROWSER_HEADERS: HeadersInit = { "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36", - accept: - "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", + accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", "accept-language": "en-US,en;q=0.9", }; @@ -169,10 +168,7 @@ function collectFileLinks(filePath: string): Array<{ url: string; line: number } return links; } -async function fetchWithTimeout( - url: string, - init: RequestInit = {}, -): Promise { +async function fetchWithTimeout(url: string, init: RequestInit = {}): Promise { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS); diff --git a/apps/docs/scripts/lint-links.ts b/apps/docs/scripts/lint-links.ts index 2a1bd6cf03..81d1f87718 100644 --- a/apps/docs/scripts/lint-links.ts +++ b/apps/docs/scripts/lint-links.ts @@ -1,15 +1,10 @@ -import { - type FileObject, - printErrors, - scanURLs, - validateFiles, -} from 'next-validate-link'; -import type { InferPageType } from 'fumadocs-core/source'; +import { type FileObject, printErrors, scanURLs, validateFiles } from "next-validate-link"; +import type { InferPageType } from "fumadocs-core/source"; -import { register } from 'node:module'; -register('fumadocs-mdx/node/loader', import.meta.url); +import { register } from "node:module"; +register("fumadocs-mdx/node/loader", import.meta.url); -const { source, sourceV6 } = await import('@/lib/source'); +const { source, sourceV6 } = await import("@/lib/source"); const v7Pages = source.getPages().map((page) => { return { value: { slug: page.slugs }, @@ -27,10 +22,10 @@ console.log(`Found ${v7Pages.length} v7 files and ${v6Pages.length} v6 files`); async function checkLinks() { const scanned = await scanURLs({ - preset: 'next', + preset: "next", populate: { - '(docs)/(default)/[[...slug]]': v7Pages, - '(docs)/v6/[[...slug]]': v6Pages, + "(docs)/(default)/[[...slug]]": v7Pages, + "(docs)/v6/[[...slug]]": v6Pages, }, }); @@ -39,27 +34,29 @@ async function checkLinks() { scanned, markdown: { components: { - Card: { attributes: ['href'] }, - Cards: { attributes: ['href'] }, + Card: { attributes: ["href"] }, + Cards: { attributes: ["href"] }, }, }, - checkRelativePaths: 'as-url', + checkRelativePaths: "as-url", }), true, ); } -function getHeadings({ data }: InferPageType | InferPageType): string[] { +function getHeadings({ + data, +}: InferPageType | InferPageType): string[] { return data.toc.map((item) => item.url.slice(1)); } function getFiles() { - console.log("Validating Files") + console.log("Validating Files"); const v7Promises = source.getPages().map( async (page): Promise => ({ - path: page.absolutePath ?? '', - content: await page.data.getText('raw'), + path: page.absolutePath ?? "", + content: await page.data.getText("raw"), url: page.url, data: page.data, }), @@ -67,8 +64,8 @@ function getFiles() { const v6Promises = sourceV6.getPages().map( async (page): Promise => ({ - path: page.absolutePath ?? '', - content: await page.data.getText('raw'), + path: page.absolutePath ?? "", + content: await page.data.getText("raw"), url: page.url, data: page.data, }), diff --git a/apps/docs/source.config.ts b/apps/docs/source.config.ts index ad18f0a866..b3b0e2d770 100644 --- a/apps/docs/source.config.ts +++ b/apps/docs/source.config.ts @@ -22,10 +22,14 @@ function convertLine(cmd: string, pm: "npm" | "pnpm" | "yarn" | "bun"): string { const flags = m[2].replace(/^\s+--\s*/, " ").trim(); // strip -- separator const f = flags ? ` ${flags}` : ""; switch (pm) { - case "npm": return line; - case "pnpm": return `pnpm create ${pkg}${f}`; - case "yarn": return `yarn create ${pkg}${f}`; - case "bun": return `bun create ${pkg}${f}`; + case "npm": + return line; + case "pnpm": + return `pnpm create ${pkg}${f}`; + case "yarn": + return `yarn create ${pkg}${f}`; + case "bun": + return `bun create ${pkg}${f}`; } } return convert(line.replace(/^npm init -y$/, "npm init"), pm); diff --git a/apps/docs/src/app/(docs)/(default)/layout.tsx b/apps/docs/src/app/(docs)/(default)/layout.tsx index 6209693742..4cc9d45584 100644 --- a/apps/docs/src/app/(docs)/(default)/layout.tsx +++ b/apps/docs/src/app/(docs)/(default)/layout.tsx @@ -16,7 +16,8 @@ import { BadgeProvider, SidebarBadgeItem } from "@/components/sidebar-badge-prov const SIDEBAR_SLIDES = [ { title: "The Next Evolution of Prisma ORM", - description: "Prisma Next: a full TypeScript rewrite with a new query API, SQL builder, and extensible architecture.", + description: + "Prisma Next: a full TypeScript rewrite with a new query API, SQL builder, and extensible architecture.", href: "https://pris.ly/pn-anouncement", gradient: "orm" as const, badge: "New", diff --git a/apps/docs/src/app/(docs)/sitemap.ts b/apps/docs/src/app/(docs)/sitemap.ts index 2f0512a884..1993c4eeb0 100644 --- a/apps/docs/src/app/(docs)/sitemap.ts +++ b/apps/docs/src/app/(docs)/sitemap.ts @@ -38,9 +38,5 @@ export default async function sitemap(): Promise { } as MetadataRoute.Sitemap[number]; }); - return [ - ...items.filter((v) => v !== undefined), - ...v6Items.filter((v) => v !== undefined), - ]; + return [...items.filter((v) => v !== undefined), ...v6Items.filter((v) => v !== undefined)]; } - diff --git a/apps/docs/src/app/(docs)/v6/[[...slug]]/page.tsx b/apps/docs/src/app/(docs)/v6/[[...slug]]/page.tsx index 9e47ff4b6b..1b52c9f8a0 100644 --- a/apps/docs/src/app/(docs)/v6/[[...slug]]/page.tsx +++ b/apps/docs/src/app/(docs)/v6/[[...slug]]/page.tsx @@ -17,20 +17,13 @@ import { EditOnGitHub, PageLastUpdate, } from "@/components/layout/notebook/page"; -import { - TechArticleSchema, - BreadcrumbSchema, -} from "@/components/structured-data"; +import { TechArticleSchema, BreadcrumbSchema } from "@/components/structured-data"; interface PageParams { slug?: string[]; } -export default async function Page({ - params, -}: { - params: Promise; -}) { +export default async function Page({ params }: { params: Promise }) { const { slug } = await params; const source = sourceV6; const page = source.getPage(slug); @@ -42,11 +35,7 @@ export default async function Page({ <> - +
{page.data.title}
@@ -73,9 +62,7 @@ export default async function Page({ href={`https://github.com/prisma/docs/edit/main/content/docs/${page.path}`} /> {(page.data as { lastModified?: Date }).lastModified && ( - + )}
diff --git a/apps/docs/src/app/(docs)/v6/layout.tsx b/apps/docs/src/app/(docs)/v6/layout.tsx index 43df0407e7..f5e1363934 100644 --- a/apps/docs/src/app/(docs)/v6/layout.tsx +++ b/apps/docs/src/app/(docs)/v6/layout.tsx @@ -5,55 +5,51 @@ import { DocsLayout } from "@/components/layout/notebook"; import { sourceV6 } from "@/lib/source"; import { DiscordIcon } from "@/components/icons/discord"; -export default async function Layout({ - children, -}: { - children: React.ReactNode; -}) { +export default async function Layout({ children }: { children: React.ReactNode }) { const source = sourceV6; const { nav, ...base } = baseOptions(); const v6Links: LinkItemType[] = [ { - text: 'Getting Started', - url: '/v6', + text: "Getting Started", + url: "/v6", }, { - text: 'ORM', - url: '/v6/orm', - active: 'nested-url', + text: "ORM", + url: "/v6/orm", + active: "nested-url", }, { - text: 'Postgres', - url: '/v6/postgres', - active: 'nested-url', + text: "Postgres", + url: "/v6/postgres", + active: "nested-url", }, { - text: 'Accelerate', - url: '/v6/accelerate', - active: 'nested-url', + text: "Accelerate", + url: "/v6/accelerate", + active: "nested-url", }, { - text: 'Guides', - url: '/v6/guides', - active: 'nested-url', + text: "Guides", + url: "/v6/guides", + active: "nested-url", }, { - text: 'Platform', - url: '/v6/platform', - active: 'nested-url', + text: "Platform", + url: "/v6/platform", + active: "nested-url", }, { - text: 'AI', - url: '/v6/ai', - active: 'nested-url', + text: "AI", + url: "/v6/ai", + active: "nested-url", }, { - type: 'icon', - label: 'Join Discord', + type: "icon", + label: "Join Discord", icon: , - text: 'Discord', - url: 'https://pris.ly/discord?utm_source=docs&utm_medium=header', + text: "Discord", + url: "https://pris.ly/discord?utm_source=docs&utm_medium=header", }, ]; @@ -77,4 +73,3 @@ export default async function Layout({ ); } - diff --git a/apps/docs/src/app/api/github-webhook/route.ts b/apps/docs/src/app/api/github-webhook/route.ts index 9039012542..c8e401b293 100644 --- a/apps/docs/src/app/api/github-webhook/route.ts +++ b/apps/docs/src/app/api/github-webhook/route.ts @@ -7,7 +7,9 @@ export async function POST(req: Request) { const payload = await req.text(); const sig = req.headers.get("x-hub-signature-256") ?? ""; - const expectedBuf = createHmac("sha256", process.env.GITHUB_WEBHOOK_SECRET!).update(payload).digest(); + const expectedBuf = createHmac("sha256", process.env.GITHUB_WEBHOOK_SECRET!) + .update(payload) + .digest(); const sigBuf = Buffer.from(sig.replace("sha256=", ""), "hex"); if (expectedBuf.length !== sigBuf.length || !timingSafeEqual(expectedBuf, sigBuf)) { return new Response("Unauthorized", { status: 401 }); @@ -42,12 +44,12 @@ export async function POST(req: Request) { teamSlugs.map((slug) => fetch( `https://api.github.com/orgs/prisma/teams/${encodeURIComponent(slug)}/memberships/${encodeURIComponent(pr.user.login)}`, - { headers: authHeaders } - ) - ) + { headers: authHeaders }, + ), + ), ); const isTeamMember = membershipResults.some((r) => r.status === 200); - + if (isMember || isTeamMember) { return new Response("Internal contributor, skipping", { status: 200 }); } @@ -97,7 +99,9 @@ export async function POST(req: Request) { return new Response("Failed to create Linear issue", { status: 500 }); } - const result = data as { data?: { issueCreate?: { success: boolean; issue?: { identifier: string; url: string } } } }; + const result = data as { + data?: { issueCreate?: { success: boolean; issue?: { identifier: string; url: string } } }; + }; if (!result.data?.issueCreate?.success) { console.error(`[${delivery}] Linear error:`, JSON.stringify(data)); @@ -107,4 +111,4 @@ export async function POST(req: Request) { const issue = result.data.issueCreate.issue!; console.log(`[${delivery}] Created ${issue.identifier}: ${issue.url}`); return new Response("OK", { status: 200 }); -} \ No newline at end of file +} diff --git a/apps/docs/src/app/api/newsletter/README.md b/apps/docs/src/app/api/newsletter/README.md index c6f11d56f4..9241ef2248 100644 --- a/apps/docs/src/app/api/newsletter/README.md +++ b/apps/docs/src/app/api/newsletter/README.md @@ -66,6 +66,7 @@ export default function Page() { ### Response Examples **Success (200)** + ```json { "message": "Please check your email to confirm subscription" @@ -73,6 +74,7 @@ export default function Page() { ``` **Already Subscribed (200)** + ```json { "message": "Already subscribed", @@ -81,6 +83,7 @@ export default function Page() { ``` **Error (400)** + ```json { "error": "Invalid email address" @@ -88,6 +91,7 @@ export default function Page() { ``` **Error (500)** + ```json { "error": "Newsletter service is not configured" @@ -114,6 +118,7 @@ Check that the `BREVO_API_KEY` environment variable is set correctly. ### "Failed to subscribe" Check the server logs for detailed error messages from Brevo. Common issues: + - Invalid API key - Incorrect list ID (update line 60 in route.ts if different from `15`) - Incorrect template ID (update line 61 in route.ts if different from `36`) @@ -161,8 +166,9 @@ templateId: 36, // Change to your template ID ## CORS Configuration The API is configured to allow requests from: + - https://prisma.io - https://www.prisma.io - https://prisma.io/docs -To add more origins, update the `corsHeaders` in `route.ts`. \ No newline at end of file +To add more origins, update the `corsHeaders` in `route.ts`. diff --git a/apps/docs/src/app/api/newsletter/route.ts b/apps/docs/src/app/api/newsletter/route.ts index e4ef7c1352..54db479aff 100644 --- a/apps/docs/src/app/api/newsletter/route.ts +++ b/apps/docs/src/app/api/newsletter/route.ts @@ -4,8 +4,7 @@ export const dynamic = "force-dynamic"; // CORS headers configuration const corsHeaders = { - "Access-Control-Allow-Origin": - "https://prisma.io, https://www.prisma.io, https://prisma.io/docs", + "Access-Control-Allow-Origin": "https://prisma.io, https://www.prisma.io, https://prisma.io/docs", "Access-Control-Allow-Methods": "POST, GET, OPTIONS", "Access-Control-Allow-Headers": "Content-Type, Authorization", }; @@ -110,10 +109,7 @@ export async function POST(request: Request) { }); // Handle specific Brevo errors - if ( - data?.code === "duplicate_parameter" || - data?.message?.includes("already exists") - ) { + if (data?.code === "duplicate_parameter" || data?.message?.includes("already exists")) { return NextResponse.json( { message: "Already subscribed", alreadySubscribed: true }, { status: 200, headers: corsHeaders }, @@ -122,8 +118,7 @@ export async function POST(request: Request) { return NextResponse.json( { - error: - data?.message || "Failed to subscribe. Please try again later.", + error: data?.message || "Failed to subscribe. Please try again later.", debug: process.env.NODE_ENV === "development" ? { @@ -145,8 +140,7 @@ export async function POST(request: Request) { ); } catch (error) { console.error("Newsletter subscription error:", error); - const errorMessage = - error instanceof Error ? error.message : "An unexpected error occurred"; + const errorMessage = error instanceof Error ? error.message : "An unexpected error occurred"; return NextResponse.json( { @@ -154,10 +148,7 @@ export async function POST(request: Request) { debug: process.env.NODE_ENV === "development" ? { - errorType: - error instanceof Error - ? error.constructor.name - : typeof error, + errorType: error instanceof Error ? error.constructor.name : typeof error, stack: error instanceof Error ? error.stack : undefined, } : undefined, diff --git a/apps/docs/src/app/api/search/route.ts b/apps/docs/src/app/api/search/route.ts index e7f43731f5..73393fee79 100644 --- a/apps/docs/src/app/api/search/route.ts +++ b/apps/docs/src/app/api/search/route.ts @@ -13,8 +13,7 @@ function getBreadcrumbsFromUrl(url: string): string[] { const normalized = segments[0] === "v6" ? segments.slice(1) : segments; if (normalized.length === 0) return []; // Ancestors only (exclude last = current page), or full path for section roots - const breadcrumbSegments = - normalized.length > 1 ? normalized.slice(0, -1) : normalized; + const breadcrumbSegments = normalized.length > 1 ? normalized.slice(0, -1) : normalized; return breadcrumbSegments.map((s) => s .split(/[-_]/) @@ -36,28 +35,22 @@ function removeMd(md: string): string { if (typeof md !== "string") return ""; try { return md - .replace( - /^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/gm, - "", - ) - .replace(/^([\s\t]*)([\*\-\+]|\d+\.)\s+/gm, "$1") + .replace(/^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/gm, "") + .replace(/^([\s\t]*)([*\-+]|\d+\.)\s+/gm, "$1") .replace(/\n={2,}/g, "\n") - .replace(/^[=\-]{2,}\s*$/gm, "") + .replace(/^[=-]{2,}\s*$/gm, "") .replace(/~{3}.*\n/g, "") .replace(/```[^\n]*\n([\s\S]*?)```/g, (_: string, c: string) => c.trim()) .replace(/~~/g, "") .replace(/<[^>]*>/g, "") - .replace(/\[\^.+?\](\: .*?$)?/g, "") + .replace(/\[\^.+?\](: .*?$)?/g, "") .replace(/\s{0,2}\[.*?\]: .*?$/g, "") .replace(/^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$/gm, "") - .replace(/!\[(.*?)\][\[\(].*?[\]\)]/g, "") - .replace(/\[([\s\S]*?)\]\s*[\(\[].*?[\)\]]/g, "$1") + .replace(/!\[(.*?)\][[(].*?[\])]/g, "") + .replace(/\[([\s\S]*?)\]\s*[([].*?[)\]]/g, "$1") .replace(/^(\n)?\s{0,3}>\s?/gm, "$1") - .replace( - /^(\n)?\s{0,}#{1,6}\s*( (.+))? +#+$|^(\n)?\s{0,}#{1,6}\s*( (.+))?$/gm, - "$1$3$4$6", - ) - .replace(/([\*]+)(\S)(.*?\S)??\1/g, "$2$3") + .replace(/^(\n)?\s{0,}#{1,6}\s*( (.+))? +#+$|^(\n)?\s{0,}#{1,6}\s*( (.+))?$/gm, "$1$3$4$6") + .replace(/([*]+)(\S)(.*?\S)??\1/g, "$2$3") .replace(/(^|\W)([_]+)(\S)(.*?\S)??\2($|\W)/g, "$1$3$4$5") .replace(/(`{3,})(.*?)\1/gm, "$2") .replace(/`(.+?)`/g, "$1") @@ -95,8 +88,7 @@ export const { GET } = createMixedbreadSearchAPI({ breadcrumbs, }, ]; - const heading = - item.type === "text" ? extractHeadingTitle(item.text) : ""; + const heading = item.type === "text" ? extractHeadingTitle(item.text) : ""; if (heading) chunkResults.push({ id: `${base}-heading`, diff --git a/apps/docs/src/app/global-error.tsx b/apps/docs/src/app/global-error.tsx index 62e45191c6..fd655a092b 100644 --- a/apps/docs/src/app/global-error.tsx +++ b/apps/docs/src/app/global-error.tsx @@ -3,11 +3,7 @@ import { useEffect } from "react"; import * as Sentry from "@sentry/nextjs"; import { DocsLayout } from "@/components/layout/notebook"; -export default function GlobalError({ - error, -}: { - error: Error & { digest?: string }; -}) { +export default function GlobalError({ error }: { error: Error & { digest?: string } }) { useEffect(() => { Sentry.captureException(error); }, [error]); diff --git a/apps/docs/src/app/global.css b/apps/docs/src/app/global.css index 03af5e904d..41692a91ba 100644 --- a/apps/docs/src/app/global.css +++ b/apps/docs/src/app/global.css @@ -1,9 +1,9 @@ -@import 'tailwindcss'; -@import '@prisma/eclipse/styles/globals.css'; -@import 'fumadocs-ui/css/shadcn.css'; -@import 'fumadocs-ui/css/preset.css'; -@import 'fumadocs-openapi/css/preset.css'; -@import '@prisma-docs/ui/styles'; +@import "tailwindcss"; +@import "@prisma/eclipse/styles/globals.css"; +@import "fumadocs-ui/css/shadcn.css"; +@import "fumadocs-ui/css/preset.css"; +@import "fumadocs-openapi/css/preset.css"; +@import "@prisma-docs/ui/styles"; @source "../../node_modules/streamdown/dist/*.js"; #nd-sidebar { diff --git a/apps/docs/src/app/layout.tsx b/apps/docs/src/app/layout.tsx index 4a5b22646e..cbe77b3633 100644 --- a/apps/docs/src/app/layout.tsx +++ b/apps/docs/src/app/layout.tsx @@ -30,17 +30,9 @@ export const metadata: Metadata = { export default function Layout({ children }: { children: ReactNode }) { return ( - + - ``` + To use TypeScript in this block, we need to opt-into it by setting `lang="ts"` in the `script` tag. In `App.svelte`, set the `lang` attribute to `ts`. + + ```svelte ``` + Our last step is to run the `setupTypeScript.js` file that Svelte provides so that we can enable TypeScript properly throughout the app. + + ```shell node scripts/setupTypeScript.js ``` + This script creates a `tsconfig.json` file at the root of the project and converts all the `.js` files to `.ts`. It also adds some new dependencies to the `package.json` file which support TypeScript. Reinstall the dependencies to get the new ones we need. + + ```shell npm install ``` + With these steps complete, we should be able to run the app and see everything working. + + ```shell npm run dev ``` + Open the app up at `http://localhost:5000`. ![Svelte app running at http:./imgs/svelte-typescript-1.png) @@ -86,6 +104,8 @@ Let's start building out the app by creating a component to show some users from Create a file in the `/src` directory called `Users.svelte`. Inside, add a `script` block with a function to get some users from the GitHub API. Be sure to opt-into TypeScript with the `lang` attribute. + + ```svelte ``` + In `App.svelte`, import and use the `Users` component so we can see the results from the API call logged to the console. + + ```svelte ``` + The return type of the `getUsers` function is now type-hinted as a `Promise` that resolves with an array of objects that are of type `User`. You may be wondering why we're relying on this spot to type-hint our data. It's because we can't apply a type hint to the other spots we might think to. We can't type-hint the `$: allUsersPromise` declaration, nor can we apply types in our template. Type-hinting the return of the function gives us some type safety in a way that is workable with Svelte. @@ -210,6 +240,8 @@ Let's create a `UserDetail` component so we can see some more information about Create a new file called `UserDetail.svelte` in the `/src` directory. In the component, we'll want an event dispatcher so that the parent component can listen for when we want to close this part of the UI. We'll also want a local function which calls the GitHub API for detailed user information. Finally, we'll want a template to display the info. + + ```svelte ``` + With the `UserDetails` type applied, we're now protected in the template. ## Current TypeScript Limitations in Svelte @@ -356,7 +393,6 @@ In our code above, we have a `closeDetails` event fired from the child `UserDeta /> // type error: closeTheDetails ``` - [This discussion](https://github.com/sveltejs/language-tools/issues/424) provides some insight on the topic and points to what we may see in the future for being able to type event handlers. ## Aside: Add Type-Safe Database Access with Prisma @@ -375,40 +411,54 @@ Let's start by creating a simple Node API. We'll build it with TypeScript. Start by creating a new folder and initiallizing npm. + + ```shell mkdir svelte-users-api cd svelte-users-api npm init -y ``` + Next, let's install the **dev** dependencies we'll need. + + ```shell npm install -D @prisma/cli typescript @types/node @types/express @types/cors ts-node-dev ``` + Most of the dependencies here are related to TypeScript but the first one in the list is `@prismac/cli`. This package will give us all the tooling we need for running `prisma` commands in our workspace to create our database models, run migrations, and more. Next, let's install our regular dependenceis. + + ```shell npm install @prisma/client express cors ``` + The first in the list for our regular dependencies is `@prisma/client`. The Prisma Client is what will give us type-safe access to our database. ### Initialize Prisma The Prisma CLI gives us an `init` command which takes care of creating a `/prisma` directory in our project and putting in a default model for us. Let's run that command and see what's inside. + + ```shell npx prisma init ``` + Inside the `/prisma` directory is a file called `schema.prisma`. This file uses the [Prisma Schema Language (PSL)](https://www.prisma.io/docs/concepts/components/prisma-schema#syntax) and is the place we describe all of our database tables and the relationships between them. Open up `schema.prisma` and put in a `User` model. This will represent a table which holds all of our user's data. + + ```prisma datasource db { provider = "sqlite" @@ -429,6 +479,7 @@ model User { } ``` + The `User` model has all of the same properties we were seeing from the GitHub API. This should allow us to easily swap out the calls to GitHub with calls to our own server. The `datasource db` line points to the database and connection we want to use. Prisma supports MySQL, Postgres, MSSQL, and SQLite. We'll use SQLite here as it's easy to work with and we don't need to spin anything else up. @@ -439,20 +490,26 @@ The `datasource db` line points to the database and connection we want to use. P With our model in place, we can now run a command to create and run a [migration](https://www.prisma.io/docs/concepts/components/prisma-migrate). + + ```shell npx prisma migrate dev --preview-feature ``` + The `prisma migrate` command will create a new `/prisma/migrations` directory in our project and will furnish it with the SQL needed to create our database table. It will then create the SQLite database in the `/prisma` directory and will create our `User` table. ### View the Database with Prisma Studio We can now view our database table by using [Prisma Studio](https://github.com/prisma/studio). + + ```shell npx prisma studio ``` + This will open up Prisma Studio in the browser at `http://locahost:5555`. ![Prisma Studio running at http:./imgs/svelte-typescript-6.png) @@ -467,26 +524,29 @@ With our database in place (and some data inside), we're now ready to create an Create a file at the project root called `server.ts`. This will be where we new up our Express app, our Prisma Client, and where we build out a `GET` endpoint for our data. + + ```ts -import express, { Request, Response } from "express"; -import cors from "cors"; -import { PrismaClient } from "@prisma/client"; +import express, { Request, Response } from 'express' +import cors from 'cors' +import { PrismaClient } from '@prisma/client' -const app = express(); -app.use(cors()); +const app = express() +app.use(cors()) -const prisma = new PrismaClient(); +const prisma = new PrismaClient() -app.get("/users", async (req: Request, res: Response) => { - const users = await prisma.user.findMany(); - res.json(users); -}); +app.get('/users', async (req: Request, res: Response) => { + const users = await prisma.user.findMany() + res.json(users) +}) -const PORT = process.env.PORT || 5001; -app.listen(PORT); -console.log(`Listening on http://localhost:${PORT}`); +const PORT = process.env.PORT || 5001 +app.listen(PORT) +console.log(`Listening on http://localhost:${PORT}`) ``` + When we want to access our database with Prisma, we use the `PrismaClient`. It's where we get the typings for our data models and the guarantees of type safety when accessing our database. The `prisma` variable that we've declared points to an instance of `PrismaClient`. On this instance we have access to a `user` property which points to our `User` model. We also get a number of methods that we can run on it to access the database, including a `findMany` records call, which we're using here. @@ -497,12 +557,17 @@ Let's now run the API and swap out the calls to GitHub for a call to our server. In the project for the API, use `ts-node-dev` to run the server. + + ```shell ts-node-dev server.ts ``` + In the Svelte project, let's swap our the github.com URL in the fetch call in `Users.svelte` with our API URL. + + ```svelte ``` + With this simple change, we should now be getting data from our server instead of from GitHub. ![Svelte app pulling user data from our own API](/build-an-app-with-svelte-and-typescript-PZDY3t93qAtd/imgs/svelte-typescript-8.png) @@ -531,3 +597,4 @@ With this simple change, we should now be getting data from our server instead o The type safety benefits that are now readily available in Svelte apps help to make our lives easier in the long run. Like any TypeScript project, it requires more effort upfront to make the app work. That extra effort pays dividends as time goes on since we can have more confidence about how our code works and we can catch bugs before they make it to production. Svelte and TypeScript pair up very nicely. Rounding things out with type safety on the backend using Prisma for database access makes for a compelling stack. + diff --git a/apps/blog/content/blog/build-applications-at-the-edge-with-prisma-orm-and-cloudflare-d1-preview/index.mdx b/apps/blog/content/blog/build-applications-at-the-edge-with-prisma-orm-and-cloudflare-d1-preview/index.mdx index b1c6508d0d..2d4a4d43f0 100644 --- a/apps/blog/content/blog/build-applications-at-the-edge-with-prisma-orm-and-cloudflare-d1-preview/index.mdx +++ b/apps/blog/content/blog/build-applications-at-the-edge-with-prisma-orm-and-cloudflare-d1-preview/index.mdx @@ -20,7 +20,7 @@ Cloudflare has been pioneering the edge computing landscape since the introducti Edge functions, such as [Cloudflare Workers](https://workers.cloudflare.com/), are a form of lightweight serverless compute that's distributed across the globe. They allow you to deploy and run your apps as closely as possible to your end users. -[D1](https://developers.cloudflare.com/d1/) is Cloudflare's native serverless database for edge environments. It's based on SQLite and can be used when deploying applications with Cloudflare. D1 was [initially launched in 2022](https://blog.cloudflare.com/introducing-d1). +[D1](https://developers.cloudflare.com/d1/) is Cloudflare's native serverless database for edge environments. It's based on SQLite and can be used when deploying applications with Cloudflare. D1 was [initially launched in 2022](https://blog.cloudflare.com/introducing-d1). - You don't need to specify where a Cloudflare Worker or a D1 database runs—they simply run - everywhere they need to. +You don't need to specify where a Cloudflare Worker or a D1 database runs—they simply run everywhere they need to. -Following Cloudflare's principles of geographic distribution and bringing compute and data closer to application users, D1 supports automatic read-replication: It dynamically manages the number of database instances and locations of read-only replicas based on how many queries a database is getting, and from where. + + +Following Cloudflare's principles of geographic distribution and bringing compute and data closer to application users, D1 supports automatic read-replication: It dynamically manages the number of database instances and locations of read-only replicas based on how many queries a database is getting, and from where. This means that read-queries are executed against the D1 instance that's closest to the location from where the query was issued. @@ -43,15 +44,16 @@ For write-operations, on the other hand, queries still travel to a single primar ## Prisma ORM now supports D1 🚀 (Preview) -At Prisma, we believe that Cloudflare is at the forefront of building the future of how applications are being built and deployed. +At Prisma, we believe that Cloudflare is at the forefront of building the future of how applications are being built and deployed. > You can learn more about how we think about Cloudflare as a partner in improving [Data DX](https://www.datadx.io/) in this blog post: [Developer Experience Redefined: Prisma & Cloudflare Lead the Way to Data DX](https://www.prisma.io/blog/cloudflare-partnership-qerefgvwirjq) +> [Supporting D1](https://github.com/prisma/prisma/issues/13310) has been one of the most popular feature requests for Prisma ORM on GitHub. ![](/build-applications-at-the-edge-with-prisma-orm-and-cloudflare-d1-preview/imgs/dece55108fc5c399e5466b8dc5d73e8e9509cab0-1480x1696.png) -As a strong believer in Cloudflare as a technology provider, we're thrilled to share that you can now use Prisma ORM inside Cloudflare Workers (and Pages) to access a D1 database. +As a strong believer in Cloudflare as a technology provider, we're thrilled to share that you can now use Prisma ORM inside Cloudflare Workers (and Pages) to access a D1 database. Note that this feature is based on [driver adapters](https://www.prisma.io/docs/orm/overview/databases/database-drivers#driver-adapters) which are currently in Preview, we therefore consider D1 support to be in Preview as well. @@ -71,15 +73,15 @@ In the following, you'll find step-by-step instructions to set up and deploy a C As a first step, go ahead and use `npm create` to bootstrap a plain version of a Cloudflare Worker (using Cloudflare's [`hello-world`](https://github.com/cloudflare/workers-sdk/tree/4fdd8987772d914cf50725e9fa8cb91a82a6870d/packages/create-cloudflare/templates/hello-world) template). Run the following command in your terminal: ```shell -npm create cloudflare@latest prisma-d1-example -- --type hello-world +npm create cloudflare@latest prisma-d1-example -- --type hello-world ``` - This will bring up a CLI wizard. Select all the _default_ options by hitting **Return** every time a question appears. At the end of the wizard, you should have a deployed Cloudflare Worker at the domain `https://prisma-d1-example.USERNAME.workers.dev` which simply renders "Hello World" in the browser: ![](/build-applications-at-the-edge-with-prisma-orm-and-cloudflare-d1-preview/imgs/0464e1fee8099d997e95b6aaa24a13ef5d40cbb8-1950x1392.png) + ### 2. Initialize Prisma ORM With your Worker in place, let's go ahead and set up Prisma ORM. @@ -90,20 +92,17 @@ First, navigate into the project directory and install the Prisma CLI: cd prisma-d1-example npm install prisma --save-dev ``` - Next, install the Prisma Client package as well as the driver adapter for D1: ```shell npm install @prisma/client npm install @prisma/adapter-d1 ``` - Finally, bootstrap the files required by Prisma ORM using the following command: ```shell npx prisma init --datasource-provider sqlite ``` - This command did two things: - It created a new directory called `prisma` that contains your Prisma schema file. @@ -111,7 +110,7 @@ This command did two things: In this tutorial, you won't need the `.env` file since the connection between Prisma ORM and D1 will happen through a [binding](https://developers.cloudflare.com/workers/configuration/bindings/). You'll find instructions for setting up this binding in the next step. -Since you'll be using the driver adapter feature which is currently in Preview, you need to explicitly enable it via the `previewFeatures` field on the `generator` block. +Since you'll be using the driver adapter feature which is currently in Preview, you need to explicitly enable it via the `previewFeatures` field on the `generator` block. Open your `schema.prisma` file and adjust the `generator` block to look as follows: @@ -121,7 +120,6 @@ prisma generator client { previewFeatures = ["driverAdapters"] } ``` - ### 3. Create D1 database In this step, you'll set up your D1 database. There generally are two approaches to this. Either using the Cloudflare Dashboard UI or via the [`wrangler`](https://developers.cloudflare.com/workers/wrangler/) CLI. You'll use the CLI in this tutorial. @@ -131,7 +129,6 @@ Open your terminal and run the following command: ```shell npx wrangler d1 create prisma-demo-db ``` - If everything went well, you should see an output similar to this: ```shell @@ -144,7 +141,6 @@ binding = "DB" # i.e. available in your Worker on env.DB database_name = "prisma-demo-db" database_id = "__YOUR_D1_DATABASE_ID__" ``` - You now have a D1 database in your Cloudflare account with a binding to your Cloudflare Worker. Copy the last part of the command output and paste it into your `wrangler.toml` file. It should look similar to this: @@ -160,7 +156,6 @@ binding = "DB" # i.e. available in your Worker on env.DB database_name = "prisma-demo-db" database_id = "__YOUR_D1_DATABASE_ID__" ``` - Note that `__YOUR_D1_DATABASE_ID__` in the snippet above is a placeholder that should be replaced with the database ID of your own D1 instance. If you weren't able to grab this ID from the terminal output, you can also find it in the [Cloudflare Dashboard](https://dash.cloudflare.com/) or by running `npx wrangler d1 info prisma-demo-db` in your terminal. Next, you'll create a database table in the database in order to be able to send some queries to D1 using Prisma ORM. @@ -168,7 +163,6 @@ Next, you'll create a database table in the database in order to be able to send ### 4. Create a table in the database D1 comes with its own [migration system](https://developers.cloudflare.com/d1/reference/migrations) via the `wrangler d1 migrate` commands. This migration system plays nicely together with the Prisma CLI which provides tools that allow you to generate SQL statements for schema changes. So you can: - - use D1's native migration system to create and apply migration files to your D1 instance - use the Prisma CLI to generate the SQL statements for any schema changes @@ -179,7 +173,6 @@ First, create a new migration using the `wrangler` CLI: ```shell npx wrangler d1 migrations create prisma-demo-db create_user_table ``` - When prompted if the command can create a new folder called `migrations`, hit **Return** to confirm. The command has now created a new directory called `migrations` and an empty file called `0001_create_user_table.sql` inside of it: @@ -188,7 +181,6 @@ The command has now created a new directory called `migrations` and an empty fil migrations/ └── 0001_create_user_table.sql ``` - Next, you need to add the SQL statement that will create a `User` table to that file. Open the `schema.prisma` file and add the following `User` model to it: ```prisma @@ -198,13 +190,11 @@ model User { name String? } ``` - Now, run the following command in your terminal to generate the SQL statement that creates a `User` table equivalent to the `User` model above: ```shell npx prisma migrate diff --from-empty --to-schema-datamodel ./prisma/schema.prisma --script --output migrations/0001_create_user_table.sql ``` - This stores a SQL statement to create a new `User` table in your migration file `migrations/0001_ceate_user_table.sql` from before, here is what it looks like: ```sql @@ -218,12 +208,12 @@ CREATE TABLE "User" ( -- CreateIndex CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); ``` - You now need to use the `wrangler d1 migrations apply` command to send this SQL statement to D1. This command accepts two options: - `--local`: Executes the statement against a _local_ version of D1. This local version of D1 is a SQLite database file that'll be located in the `.wrangler/state` directory of your project. This approach is useful, when you want to develop and test your Worker on your local machine. Learn more in the [Cloudflare docs](https://developers.cloudflare.com/d1/configuration/local-development/). - `--remote`: Executes the statement against your _remote_ version of D1. This version is used by your _deployed_ Cloudflare Workers. Learn more in the [Cloudflare docs](https://developers.cloudflare.com/d1/configuration/remote-development/). + In this tutorial, you’ll do both: test the Worker locally _and_ deploy it afterwards. So, you need to run both commands. Open your terminal and paste the following commands. First, execute the schema changes against your _local_ database: @@ -231,13 +221,11 @@ First, execute the schema changes against your _local_ database: ```shell npx wrangler d1 migrations apply prisma-demo-db --local ``` - Next, against the remote database: ```shell npx wrangler d1 migrations apply prisma-demo-db --remote ``` - Hit **Return** both times when you're prompted to confirm that the migration should be applied. Both your local and remote D1 instances now contain `User` table. @@ -247,19 +235,18 @@ Let’s also create some dummy data that we can query once the Worker is running Again, run the command against your _local_ database first: ```shell -npx wrangler d1 execute prisma-demo-db --command "INSERT INTO \"User\" (\"email\", \"name\") VALUES +npx wrangler d1 execute prisma-demo-db --command "INSERT INTO \"User\" (\"email\", \"name\") VALUES ('jane@prisma.io', 'Jane Doe (Local)');" --local ``` - Finally, run it against your _remote_ database: ```shell -npx wrangler d1 execute prisma-demo-db --command "INSERT INTO \"User\" (\"email\", \"name\") VALUES +npx wrangler d1 execute prisma-demo-db --command "INSERT INTO \"User\" (\"email\", \"name\") VALUES ('jane@prisma.io', 'Jane Doe (Remote)');" --remote ``` - You now have a dummy record in both your local and remote database instances. You can find the local SQLite file in `.wrangler/state` while the remote one can be inspected in your Cloudflare Dashboard. + ### 5. Query your database from the Worker In order to query your database from the Worker using Prisma ORM, you need to: @@ -271,31 +258,29 @@ In order to query your database from the Worker using Prisma ORM, you need to: Open `src/index.ts` and replace the entire content with the following: ```ts -import { PrismaClient } from "@prisma/client"; -import { PrismaD1 } from "@prisma/adapter-d1"; +import { PrismaClient } from '@prisma/client' +import { PrismaD1 } from '@prisma/adapter-d1' export interface Env { - DB: D1Database; + DB: D1Database } export default { - async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise { - const adapter = new PrismaD1(env.DB); - const prisma = new PrismaClient({ adapter }); + async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise { + const adapter = new PrismaD1(env.DB) + const prisma = new PrismaClient({ adapter }) - const users = await prisma.user.findMany(); - const result = JSON.stringify(users); + const users = await prisma.user.findMany() + const result = JSON.stringify(users) return new Response(result); - }, + }, }; ``` - Before running the Worker, you need to generate Prisma Client with the following command: ```shell npx prisma generate ``` - ### 6. Run the Worker locally With the database query in place and Prisma Client generated, you can go ahead and run the Worker locally: @@ -303,13 +288,11 @@ With the database query in place and Prisma Client generated, you can go ahead a ```shell npm run dev ``` - Now you can open your browser at [`http://localhost:8787`](http://localhost:8787/) to see the result of the database query: ```json -[{ "id": 1, "email": "jane@prisma.io", "name": "Jane Doe (Local)" }] +[{"id":1,"email":"jane@prisma.io","name":"Jane Doe (Local)"}] ``` - ### 7. Deploy the Worker To deploy the Worker, run the the following command: @@ -317,9 +300,9 @@ To deploy the Worker, run the the following command: ```shell npm run deploy ``` - As before, your deployed Worker is accessible via `https://prisma-d1-example.USERNAME.workers.dev`. If you navigate your browser to that URL, you should see the following data that's queried from your remote D1 database: + ![](/build-applications-at-the-edge-with-prisma-orm-and-cloudflare-d1-preview/imgs/a695edcc461f4bc01a9eac9d3a103d8ef2ab9dfd-1960x1386.png) Congratulations, you just deployed a Cloudflare Worker using D1 as a database and querying it via Prisma ORM 🎉 diff --git a/apps/blog/content/blog/caching-database-queries-with-prisma-accelerate/index.mdx b/apps/blog/content/blog/caching-database-queries-with-prisma-accelerate/index.mdx index 34eda69161..938acabde2 100644 --- a/apps/blog/content/blog/caching-database-queries-with-prisma-accelerate/index.mdx +++ b/apps/blog/content/blog/caching-database-queries-with-prisma-accelerate/index.mdx @@ -8,7 +8,7 @@ metaTitle: "Cache your database queries with Prisma Accelerate" metaDescription: "Achieve faster performance and cost savings with Prisma Accelerate's easy per-query caching, eliminating the need for maintaining caching infrastructure. Experience faster queries, without the hassle!" metaImagePath: "/caching-database-queries-with-prisma-accelerate/imgs/meta-959743e064c1771e65e078aa6ebee2b402c28578-1266x711.png" heroImagePath: "/caching-database-queries-with-prisma-accelerate/imgs/hero-69cd8d277e2307eb14396450817be41bb1e7c305-844x474.svg" -heroImageAlt: 'The word "caching" with a starburst behind it, showing that it is important.' +heroImageAlt: "The word \"caching\" with a starburst behind it, showing that it is important." --- Dive into the benefits of per-query caching, showcasing how it can make queries faster, handle traffic surges, minimize infrastructure costs, and keep your users satisfied. Learn how to easily implement Prisma Accelerate and achieve improved app performance and cost savings. @@ -17,7 +17,7 @@ Picture this: You and your team just released your latest app, SuperWidget. Ever To solve this, your team jumps into action quickly. You dive into your application monitoring and realize that several queries in your application have a much larger impact than anticipated. After a long night, your team implements a number of infrastructure improvements, most notably a robust caching layer, which takes the load off the rest of your infrastructure. SuperWidget performance is no longer being negatively impacted, and your new customers are happy with their experience. -So, what could you and your team have done better? +So, what could you and your team have done better? While your team was capable, fire drills and all-nighters are the last thing you want. The solution still required a coordinated effort and a lot of wasted engineering hours. Instead, you could have saved time and frustration by using [Prisma Accelerate](https://www.prisma.io/data-platform/accelerate?utm_source=website&utm_medium=blogpost&utm_campaign=caching) and caching your queries with ease. @@ -42,14 +42,13 @@ const myExpensiveQuery = await prisma.widget.findMany({ swr: 60 * 2, // serve from cache for 2 more minutes and revalidate in the background }, // [...] -}); +}) ``` - In the first case, where expensive or frequently accessed queries can overwhelm your database, a per-query cache will prevent that load for as long as the data is cached. If cost is the concern, your data will be accessed from your database once and then cached in Accelerate’s collection of globally distributed nodes, preventing additional costs from further database reads while also making your app faster! ### When to cache -Now that you know why and how to cache, you may be tempted to begin caching every query. Before you do that, note what happened in our example: you and your team monitored your application _before_ implementing caching. +Now that you know why and how to cache, you may be tempted to begin caching every query. Before you do that, note what happened in our example: you and your team monitored your application *before* implementing caching. Caching is great, but any addition can incur a cost or cause unintended side effects. At Prisma, we’re big fans of observability-driven development: instrument your app and make informed decisions. Caching should be a carefully considered option among many. If, for example, you need a certain query to always contain exactly up-to-date data, then caching will probably not be the right fit. On the other hand, cases where data doesn’t change often or doesn’t need to be up-to-date are great fits. @@ -81,11 +80,10 @@ Accelerate is also a great fit for teams that want to onboard engineers fast. Si ### Wrapping up -Caching is still one of the “hard” problems of software engineering and rightfully so! Simply caching more things won’t solve problems and a team needs to take the time to understand what can be cached and how it will impact their product. +Caching is still one of the “hard” problems of software engineering and rightfully so! Simply caching more things won’t solve problems and a team needs to take the time to understand what can be cached and how it will impact their product. While there are many different approaches to caching, Prisma Accelerate makes it easy to implement caching on a pre-query basis and gives you more time to focus on building great products. If you’d like to take the next step, [learn more about Accelerate](https://www.prisma.io/data-platform/accelerate?utm_source=website&utm_medium=blogpost&utm_campaign=caching) and [get started](https://www.prisma.io/docs/accelerate/getting-started) caching your Prisma ORM queries today.

-[Start caching with -Accelerate](https://console.prisma.io/login?utm_source=website&utm_medium=blogpost&utm_campaign=caching) +[Start caching with Accelerate](https://console.prisma.io/login?utm_source=website&utm_medium=blogpost&utm_campaign=caching) diff --git a/apps/blog/content/blog/client-extensions-ga-4g4yIu8eOSbB/index.mdx b/apps/blog/content/blog/client-extensions-ga-4g4yIu8eOSbB/index.mdx index 07c9c55e8f..e333d3fd86 100644 --- a/apps/blog/content/blog/client-extensions-ga-4g4yIu8eOSbB/index.mdx +++ b/apps/blog/content/blog/client-extensions-ga-4g4yIu8eOSbB/index.mdx @@ -28,32 +28,34 @@ If this is the first time you're hearing about Client extensions, don't worry. W This code snippet shows how you can add a _new method_ to the `User` model using a [`model`](https://www.prisma.io/docs/orm/prisma-client/client-extensions/model) extension: ```typescript -import { PrismaClient } from "@prisma/client"; +import { PrismaClient } from '@prisma/client'; const prisma = new PrismaClient().$extends({ model: { user: { - async signUp(email: string) { + async signUp(email: string) { // code for the new method goes inside the brackets - }, + }, }, }, }); // The new method can then be used like this: -const newUser = await prisma.user.signUp("myemail@email.com"); +const newUser = await prisma.user.signUp('myemail@email.com'); ``` - If you instead require a method on _all models_, you can even use the builtin `$allModels` feature: ```typescript -import { PrismaClient } from "@prisma/client"; +import { PrismaClient } from '@prisma/client'; const prisma = new PrismaClient().$extends({ model: { $allModels: { - async exists(this: T, where: Prisma.Args["where"]): Promise { + async exists( + this: T, + where: Prisma.Args['where'] + ): Promise { // code for the new method goes inside the brackets }, }, @@ -64,7 +66,6 @@ const prisma = new PrismaClient().$extends({ const postExists = await prisma.post.exists({ id: 1 }); ``` - For a more in-depth look into changes we made to the extensions API as a part of this release, please check out [our release notes](https://github.com/prisma/prisma/releases/tag/4.16.0) ### Extensions built by the community @@ -72,8 +73,8 @@ For a more in-depth look into changes we made to the extensions API as a part of While client extensions are now generally available, we have already seen some cool examples in the wild. [`prisma-extension-pagination`](https://github.com/deptyped/prisma-extension-pagination) is an awesome contribution from our community. Importing and using an external client extension is easy too: ```typescript -import { PrismaClient } from "@prisma/client"; -import pagination from "prisma-extension-pagination"; +import { PrismaClient } from '@prisma/client'; +import pagination from 'prisma-extension-pagination'; const prisma = new PrismaClient().$extends(pagination); @@ -81,13 +82,12 @@ const [users, meta] = prisma.user .paginate({ select: { id: true, - }, + } }) .withPages({ limit: 10, }); ``` - ### Reference examples for various use cases In addition to community contributions, we have a set of reference examples in the [`prisma-client-extensions` example repository](https://github.com/prisma/prisma-client-extensions) that showcase different areas where we believe Prisma Client extensions can be useful. The repository currently contains the following example extensions: @@ -99,7 +99,7 @@ In addition to community contributions, we have a set of reference examples in t | [computed-fields](https://github.com/prisma/prisma-client-extensions/tree/main/computed-fields) | Adds virtual / computed fields to result objects | | [input-transformation](https://github.com/prisma/prisma-client-extensions/tree/main/input-transformation) | Transforms the input arguments passed to Prisma Client queries to filter the result set | | [input-validation](https://github.com/prisma/prisma-client-extensions/tree/main/input-validation) | Runs custom validation logic on input arguments passed to mutation methods | -| [instance-methods](https://github.com/prisma/prisma-client-extensions/tree/main/instance-methods) | Adds Active Record-like methods like `save()` and `delete()` to result objects | +| [instance-methods](https://github.com/prisma/prisma-client-extensions/tree/main/instance-methods) | Adds Active Record-like methods like `save()` and `delete()` to result objects | | [json-field-types](https://github.com/prisma/prisma-client-extensions/tree/main/json-field-types) | Uses strongly-typed runtime parsing for data stored in JSON columns | | [model-filters](https://github.com/prisma/prisma-client-extensions/tree/main/model-filters) | Adds reusable filters that can composed into complex where conditions for a model | | [obfuscated-fields](https://github.com/prisma/prisma-client-extensions/tree/main/obfuscated-fields) | Prevents sensitive data (e.g. password fields) from being included in results | @@ -109,12 +109,12 @@ In addition to community contributions, we have a set of reference examples in t | [row-level-security](https://github.com/prisma/prisma-client-extensions/tree/main/row-level-security) | Uses Postgres row-level security policies to isolate data a multi-tenant application | | [static-methods](https://github.com/prisma/prisma-client-extensions/tree/main/static-methods) | Adds custom query methods to Prisma Client models | | [transformed-fields](https://github.com/prisma/prisma-client-extensions/tree/main/transformed-fields) | Demonstrates how to use result extensions to transform query results and add i18n to an app | -| [exists-fn](https://github.com/prisma/prisma-client-extensions/tree/main/exists-fn) | Demonstrates how to add an exists method to all your models | +| [exists-fn](https://github.com/prisma/prisma-client-extensions/tree/main/exists-fn) | Demonstrates how to add an exists method to all your models | ### Show off your extensions! -If you'd like a deeper dive into Prisma Client extensions, be sure to check out our previous write-up: [Prisma Client Just Became a Lot More Flexible: Prisma Client Extensions](/client-extensions-preview-8t3w27xkrxxn)! +If you'd like a deeper dive into Prisma Client extensions, be sure to check out our previous write-up: [Prisma Client Just Became a Lot More Flexible: Prisma Client Extensions](/client-extensions-preview-8t3w27xkrxxn)! -We'd also love to hear about your extensions (and maybe even take them for a spin). +We'd also love to hear about your extensions (and maybe even take them for a spin). Be sure to show of your `#MadeWithPrisma` work in our [Discord](https://discord.gg/KQyTW2H5ca) diff --git a/apps/blog/content/blog/client-extensions-preview-8t3w27xkrxxn/index.mdx b/apps/blog/content/blog/client-extensions-preview-8t3w27xkrxxn/index.mdx index 7ba8e4a81a..f1153c6bae 100644 --- a/apps/blog/content/blog/client-extensions-preview-8t3w27xkrxxn/index.mdx +++ b/apps/blog/content/blog/client-extensions-preview-8t3w27xkrxxn/index.mdx @@ -58,14 +58,12 @@ generator client { previewFeatures = ["clientExtensions"] } ``` - Then, you can call the `$extends` method on a Prisma Client instance. This will return a new, "extended" client instance without modifying the original instance. You can chain calls to `$extends` to use multiple extensions, and create separate instances with different extensions: ```typescript const prisma = new PrismaClient(); const extendedPrisma = prisma.$extends(myExtensionA).$extends(myExtensionB); ``` - ### The components of an extension There are four different types of components that can be included in an extension: @@ -80,21 +78,12 @@ A single extension can include one or more components, as well as an optional na ```typescript const prisma = new PrismaClient().$extends({ name: "myExtension", - model: { - /* ... */ - }, - client: { - /* ... */ - }, - query: { - /* ... */ - }, - result: { - /* ... */ - }, + model: { /* ... */ }, + client: { /* ... */ }, + query: { /* ... */ }, + result: { /* ... */ }, }); ``` - To see the full syntax for defining each type of extension component, please refer to [the docs](https://www.prisma.io/docs/concepts/components/prisma-client/client-extensions). ### Sharing an extension @@ -113,7 +102,6 @@ export default Prisma.defineExtension({ }, }); ``` - When publishing shared extensions to npm, we recommend using the `prisma-extension-` convention. This will make it easier for users to find and install your extension in their apps. For example, if you publish an extension with the package name `prisma-extension-find-or-create`, users can install it like: @@ -121,7 +109,6 @@ For example, if you publish an extension with the package name `prisma-extension ```sh npm install prisma-extension-find-or-create ``` - And then use the extension in their app: ```typescript @@ -129,11 +116,8 @@ import { PrismaClient } from "@prisma/client"; import findOrCreate from "prisma-extension-find-or-create"; const prisma = new PrismaClient().$extends(findOrCreate); -const user = await prisma.user.findOrCreate({ - /* ... */ -}); +const user = await prisma.user.findOrCreate({ /* ... */ }); ``` - Read [our documentation on sharing extensions](https://www.prisma.io/docs/concepts/components/prisma-client/client-extensions/shared-extensions) for more details. ## Sample use cases @@ -156,7 +140,7 @@ Computed fields are type-safe and may return anything from simple values to comp import { PrismaClient } from "@prisma/client"; const prisma = new PrismaClient() -.$extends({ + .$extends({ result: { user: { fullName: { @@ -169,27 +153,25 @@ const prisma = new PrismaClient() }, }) .$extends({ -result: { -user: { -displayName: { -needs: { fullName: true, email: true }, -compute(user) { -return `${user.fullName} <${user.email}>`; -}, -}, -}, -}, -}); - -```` + result: { + user: { + displayName: { + needs: { fullName: true, email: true }, + compute(user) { + return `${user.fullName} <${user.email}>`; + }, + }, + }, + }, + }); +``` ```typescript const users = await prisma.user.findMany({ take: 5 }); for (const user of users) { console.info(`- ${user.displayName}`); } -```` - +``` ```prisma model User { id String @id @default(cuid()) @@ -198,7 +180,6 @@ model User { email String } ``` - @@ -218,30 +199,28 @@ import { de } from "date-fns/locale"; import { PrismaClient } from "@prisma/client"; const prisma = new PrismaClient().$extends({ -result: { -post: { -createdAt: { -needs: { createdAt: true }, -compute(post) { -return formatDistanceToNow(post.createdAt, { -addSuffix: true, -locale: de, -}); -}, -}, -}, -}, + result: { + post: { + createdAt: { + needs: { createdAt: true }, + compute(post) { + return formatDistanceToNow(post.createdAt, { + addSuffix: true, + locale: de, + }); + }, + }, + }, + }, }); - -```` +``` ```typescript const posts = await prisma.post.findMany({ take: 5 }); for (const post of posts) { console.info(`- ${post.title} (${post.createdAt})`); } -```` - +``` ```prisma model Post { id String @id @default(cuid()) @@ -249,7 +228,6 @@ model Post { createdAt DateTime @default(now()) } ``` - @@ -265,25 +243,23 @@ This example is a special case for the previous [Transformed fields](#example-tr import { PrismaClient } from "@prisma/client"; const prisma = new PrismaClient().$extends({ -result: { -user: { -password: { -needs: {}, -compute() { -return undefined; -}, -}, -}, -}, + result: { + user: { + password: { + needs: {}, + compute() { + return undefined; + }, + }, + }, + }, }); - -```` +``` ```typescript const user = await prisma.user.findFirstOrThrow(); console.info("Email: ", user.email); // "user@example.com" console.info("Password: ", user.password); // undefined -```` - +``` ```prisma model User { id String @id @default(cuid()) @@ -291,7 +267,6 @@ model User { password String } ``` - @@ -309,14 +284,14 @@ This technique can be used to customize Prisma result objects with behavior, ana import { PrismaClient } from "@prisma/client"; const prisma = new PrismaClient().$extends({ -result: { -user: { -save: { -needs: { id: true, email: true }, -compute({ id, email }) { -return () => prisma.user.update({ where: { id }, data: { email } }); -}, -}, + result: { + user: { + save: { + needs: { id: true, email: true }, + compute({ id, email }) { + return () => prisma.user.update({ where: { id }, data: { email } }); + }, + }, delete: { needs: { id: true }, @@ -325,11 +300,9 @@ return () => prisma.user.update({ where: { id }, data: { email } }); }, }, }, - -}, + }, }); - -```` +``` ```typescript const user = await prisma.user.create({ data: { email: "test@example.com" }, @@ -337,15 +310,13 @@ const user = await prisma.user.create({ user.email = "updated@example.com"; await user.save(); await user.delete(); -```` - +``` ```prisma model User { id String @id @default(cuid()) email String } ``` - @@ -364,21 +335,21 @@ import bcrypt from "bcryptjs"; import { PrismaClient } from "@prisma/client"; const prisma = new PrismaClient().$extends({ -model: { -user: { -async signUp(email: string, password: string) { -const hash = await bcrypt.hash(password, 10); -return prisma.user.create({ -data: { -email, -password: { -create: { -hash, -}, -}, -}, -}); -}, + model: { + user: { + async signUp(email: string, password: string) { + const hash = await bcrypt.hash(password, 10); + return prisma.user.create({ + data: { + email, + password: { + create: { + hash, + }, + }, + }, + }); + }, async findManyByDomain(domain: string) { return prisma.user.findMany({ @@ -386,18 +357,15 @@ hash, }); }, }, - -}, + }, }); - -```` +``` ```typescript await prisma.user.signUp("user1@example1.com", "p4ssword"); await prisma.user.signUp("user2@example2.com", "s3cret"); const users = await prisma.user.findManyByDomain("example2.com"); -```` - +``` ```prisma model User { id String @id @default(cuid()) @@ -411,7 +379,6 @@ model Password { userId String @unique } ``` - @@ -434,17 +401,16 @@ const prisma = new PrismaClient().$extends({ byAuthor: (authorId: string) => ({ authorId }), byAuthorDomain: (domain: string) => ({ author: { email: { endsWith: `@${domain}` } }, -}), -hasComments: () => ({ comments: { some: {} } }), -hasRecentComments: (date: Date) => ({ -comments: { some: { createdAt: { gte: date } } }, -}), -titleContains: (search: string) => ({ title: { contains: search } }), -} satisfies Record Prisma.PostWhereInput>, -}, + }), + hasComments: () => ({ comments: { some: {} } }), + hasRecentComments: (date: Date) => ({ + comments: { some: { createdAt: { gte: date } } }, + }), + titleContains: (search: string) => ({ title: { contains: search } }), + } satisfies Record Prisma.PostWhereInput>, + }, }); - -```` +``` ```typescript const posts = await prisma.post.findMany({ where: { @@ -456,8 +422,7 @@ const posts = await prisma.post.findMany({ ], }, }); -```` - +``` ```prisma model User { id String @id @default(cuid()) @@ -487,7 +452,6 @@ model Comment { post Post @relation(fields: [postId], references: [id], onDelete: Cascade) } ``` - @@ -503,38 +467,38 @@ This example creates a client that only allows read operations like `findMany` a import { Prisma, PrismaClient } from "@prisma/client"; const WRITE_METHODS = [ -"create", -"update", -"upsert", -"delete", -"createMany", -"updateMany", -"deleteMany", + "create", + "update", + "upsert", + "delete", + "createMany", + "updateMany", + "deleteMany", ] as const; const ReadonlyClient = Prisma.defineExtension({ -name: "ReadonlyClient", -model: { -$allModels: Object.fromEntries( + name: "ReadonlyClient", + model: { + $allModels: Object.fromEntries( WRITE_METHODS.map((method) => [ method, function (args: never) { throw new Error( - `Calling the \`${method}\` method on a readonly client is not allowed` ); + `Calling the \`${method}\` method on a readonly client is not allowed` + ); }, ]) ) as { [K in typeof WRITE_METHODS[number]]: ( - args:`Calling the \`${K}\` method on a readonly client is not allowed` -) => never; -}, -}, + args: `Calling the \`${K}\` method on a readonly client is not allowed` + ) => never; + }, + }, }); const prisma = new PrismaClient(); const readonlyPrisma = prisma.$extends(ReadonlyClient); - -```` +``` ```typescript const posts = await readonlyPrisma.post.findMany({ take: 5 }); console.log(posts); @@ -546,8 +510,7 @@ console.log(posts); await readonlyPrisma.post.create({ data: { title: "New post", published: false }, }); -```` - +``` ```prisma model Post { id String @id @default(cuid()) @@ -555,10 +518,10 @@ model Post { published Boolean } ``` - + ### Example: Input transformation [View full example on GitHub](https://github.com/prisma/prisma-client-extensions/tree/main/input-transformation) @@ -592,15 +555,12 @@ const prisma = new PrismaClient().$extends({ }); }, }, - -}, + }, }); - -```` +``` ```typescript const publishedPostsCount = await prisma.post.count(); -```` - +``` ```prisma model Post { id String @id @default(cuid()) @@ -608,7 +568,6 @@ model Post { published Boolean } ``` - @@ -627,30 +586,29 @@ import { Prisma, PrismaClient } from "@prisma/client"; import { ProductCreateInput } from "./schemas"; const prisma = new PrismaClient().$extends({ -query: { -product: { -create({ args, query }) { -args.data = ProductCreateInput.parse(args.data); -return query(args); -}, -update({ args, query }) { -args.data = ProductCreateInput.partial().parse(args.data); -return query(args); -}, -updateMany({ args, query }) { -args.data = ProductCreateInput.partial().parse(args.data); -return query(args); -}, -upsert({ args, query }) { -args.create = ProductCreateInput.parse(args.create); -args.update = ProductCreateInput.partial().parse(args.update); -return query(args); -}, -}, -}, + query: { + product: { + create({ args, query }) { + args.data = ProductCreateInput.parse(args.data); + return query(args); + }, + update({ args, query }) { + args.data = ProductCreateInput.partial().parse(args.data); + return query(args); + }, + updateMany({ args, query }) { + args.data = ProductCreateInput.partial().parse(args.data); + return query(args); + }, + upsert({ args, query }) { + args.create = ProductCreateInput.parse(args.create); + args.update = ProductCreateInput.partial().parse(args.update); + return query(args); + }, + }, + }, }); - -```` +``` ```typescript import { z } from "zod"; import { Prisma } from "@prisma/client"; @@ -666,8 +624,7 @@ export const ProductCreateInput = z.object({ .instanceof(Prisma.Decimal) .refine((price) => price.gte("0.01") && price.lt("1000000.00")), }) satisfies z.Schema; -```` - +``` ```typescript // Valid product const product = await prisma.product.create({ @@ -693,7 +650,6 @@ try { console.log(err?.cause?.issues); } ``` - ```prisma model Product { id String @id @default(cuid()) @@ -712,7 +668,6 @@ model Review { productId String } ``` - @@ -734,54 +689,53 @@ import { Prisma, PrismaClient } from "@prisma/client"; import { Profile } from "./schemas"; const prisma = new PrismaClient().$extends({ -result: { -user: { -profile: { -needs: { profile: true }, -compute({ profile }) { -return Profile.parse(profile); -}, -}, -}, -}, - -query: { -user: { -create({ args, query }) { -args.data.profile = Profile.parse(args.data.profile); -return query(args); -}, -createMany({ args, query }) { -const users = Array.isArray(args.data) ? args.data : [args.data]; -for (const user of users) { -user.profile = Profile.parse(user.profile); -} -return query(args); -}, -update({ args, query }) { -if (args.data.profile !== undefined) { -args.data.profile = Profile.parse(args.data.profile); -} -return query(args); -}, -updateMany({ args, query }) { -if (args.data.profile !== undefined) { -args.data.profile = Profile.parse(args.data.profile); -} -return query(args); -}, -upsert({ args, query }) { -args.create.profile = Profile.parse(args.create.profile); -if (args.update.profile !== undefined) { -args.update.profile = Profile.parse(args.update.profile); -} -return query(args); -}, -}, -}, -}); + result: { + user: { + profile: { + needs: { profile: true }, + compute({ profile }) { + return Profile.parse(profile); + }, + }, + }, + }, -```` + query: { + user: { + create({ args, query }) { + args.data.profile = Profile.parse(args.data.profile); + return query(args); + }, + createMany({ args, query }) { + const users = Array.isArray(args.data) ? args.data : [args.data]; + for (const user of users) { + user.profile = Profile.parse(user.profile); + } + return query(args); + }, + update({ args, query }) { + if (args.data.profile !== undefined) { + args.data.profile = Profile.parse(args.data.profile); + } + return query(args); + }, + updateMany({ args, query }) { + if (args.data.profile !== undefined) { + args.data.profile = Profile.parse(args.data.profile); + } + return query(args); + }, + upsert({ args, query }) { + args.create.profile = Profile.parse(args.create.profile); + if (args.update.profile !== undefined) { + args.update.profile = Profile.parse(args.update.profile); + } + return query(args); + }, + }, + }, +}); +``` ```typescript import { z } from "zod"; @@ -834,8 +788,7 @@ export const Profile = z socialLinks: SocialLinks, }) .partial(); -```` - +``` ```typescript import * as runtime from "@prisma/client/runtime/index"; import { UserPayload } from "@prisma/client"; @@ -843,14 +796,13 @@ import { UserPayload } from "@prisma/client"; const users = await prisma.user.findMany({ take: 10 }); users.forEach(renderUser); -type ExtArgs = UserPayload<(typeof prisma)["$extends"]["extArgs"]>; +type ExtArgs = UserPayload; type User = runtime.Types.GetResult; function renderUser(user: User) { // ... } ``` - ```prisma model User { id String @id @default(cuid()) @@ -858,7 +810,6 @@ model User { profile Json } ``` - @@ -880,26 +831,25 @@ import { performance } from "perf_hooks"; import * as util from "util"; const prisma = new PrismaClient().$extends({ -query: { -$allModels: { -async $allOperations({ operation, model, args, query }) { -const start = performance.now(); -const result = await query(args); -const end = performance.now(); -const time = end - start; -console.log( -util.inspect( -{ model, operation, args, time }, -{ showHidden: false, depth: null, colors: true } -) -); -return result; -}, -}, -}, + query: { + $allModels: { + async $allOperations({ operation, model, args, query }) { + const start = performance.now(); + const result = await query(args); + const end = performance.now(); + const time = end - start; + console.log( + util.inspect( + { model, operation, args, time }, + { showHidden: false, depth: null, colors: true } + ) + ); + return result; + }, + }, + }, }); - -```` +``` ```typescript await prisma.user.findMany({ orderBy: [{ lastName: "asc" }, { firstName: "asc" }], @@ -907,8 +857,7 @@ await prisma.user.findMany({ }); await prisma.user.groupBy({ by: ["lastName"], _count: true }); -```` - +``` ```prisma model User { id String @id @default(cuid()) @@ -916,7 +865,6 @@ model User { lastName String } ``` - @@ -933,32 +881,31 @@ import { backOff, IBackOffOptions } from "exponential-backoff"; import { Prisma, PrismaClient } from "@prisma/client"; function RetryTransactions(options?: Partial) { -return Prisma.defineExtension((prisma) => -prisma.$extends({ + return Prisma.defineExtension((prisma) => + prisma.$extends({ client: { $transaction(...args: any) { return backOff(() => prisma.$transaction.apply(prisma, args), { -retry: (e) => { -// Retry the transaction only if the error was due to a write conflict or deadlock -// See: https://www.prisma.io/docs/reference/api-reference/error-reference#p2034 -return e.code === "P2034"; -}, -...options, -}); -}, -} as { $transaction: typeof prisma["$transaction"] }, -}) -); + retry: (e) => { + // Retry the transaction only if the error was due to a write conflict or deadlock + // See: https://www.prisma.io/docs/reference/api-reference/error-reference#p2034 + return e.code === "P2034"; + }, + ...options, + }); + }, + } as { $transaction: typeof prisma["$transaction"] }, + }) + ); } const prisma = new PrismaClient().$extends( -RetryTransactions({ -jitter: "full", -numOfAttempts: 5, -}) + RetryTransactions({ + jitter: "full", + numOfAttempts: 5, + }) ); - -```` +``` ```typescript // If one or more transactions fail due to deadlock / write conflict, // the entire transaction will be retried up to 5 times. @@ -973,8 +920,7 @@ await Promise.allSettled([ // ... }), ]); -```` - +``` ```prisma model User { id String @id @default(cuid()) @@ -982,7 +928,6 @@ model User { lastName String } ``` - @@ -1000,19 +945,19 @@ This gives you the full power of [interactive transactions](https://www.prisma.i import { Prisma, PrismaClient } from "@prisma/client"; type FlatTransactionClient = Prisma.TransactionClient & { -$commit: () => Promise; -$rollback: () => Promise; + $commit: () => Promise; + $rollback: () => Promise; }; const ROLLBACK = { [Symbol.for("prisma.client.extension.rollback")]: true }; const prisma = new PrismaClient().$extends({ -client: { -async $begin() { -const prisma = Prisma.getExtensionContext(this); -let setTxClient: (txClient: Prisma.TransactionClient) => void; -let commit: () => void; -let rollback: () => void; + client: { + async $begin() { + const prisma = Prisma.getExtensionContext(this); + let setTxClient: (txClient: Prisma.TransactionClient) => void; + let commit: () => void; + let rollback: () => void; // a promise for getting the tx inner client const txClient = new Promise((res) => { @@ -1061,18 +1006,15 @@ let rollback: () => void; throw new Error("Transactions are not supported by this client"); }, - -}, + }, }); - -```` +``` ```typescript const tx = await prisma.$begin(); const user = await tx.user.findFirstOrThrow(); await tx.user.update(/* ... */); await tx.$commit(); // Or: await tx.$rollback(); -```` - +``` ```prisma model User { id String @id @default(cuid()) @@ -1081,7 +1023,6 @@ model User { email String } ``` - @@ -1099,24 +1040,23 @@ A detailed explanation of this solution can be found in [the example's `README` import { Prisma, PrismaClient } from "@prisma/client"; function forUser(userId: number) { -return Prisma.defineExtension((prisma) => -prisma.$extends({ + return Prisma.defineExtension((prisma) => + prisma.$extends({ query: { $allModels: { async $allOperations({ args, query }) { const [, result] = await prisma.$transaction([ -prisma.$executeRaw`SELECT set_config('app.current_user_id', ${userId.toString()}, TRUE)`, -query(args), -]); -return result; -}, -}, -}, -}) -); + prisma.$executeRaw`SELECT set_config('app.current_user_id', ${userId.toString()}, TRUE)`, + query(args), + ]); + return result; + }, + }, + }, + }) + ); } - -```` +``` ```typescript const prisma = new PrismaClient(); const user = await prisma.user.findFirstOrThrow(); @@ -1128,8 +1068,7 @@ await userPrisma.product.update({ where: { id: product.id }, data: { name: "Updated Name" }, }); -```` - +``` ```prisma model User { id Int @id @default(autoincrement()) @@ -1175,7 +1114,6 @@ model ProductVersion { @@schema("audit") } ``` - ```sql -- Product audit trigger function CREATE OR REPLACE FUNCTION "audit"."Product_audit"() RETURNS TRIGGER AS $$ @@ -1198,7 +1136,6 @@ CREATE TRIGGER audit AFTER INSERT OR UPDATE OR DELETE ON "public"."Product" FOR EACH ROW EXECUTE FUNCTION "audit"."Product_audit"(); ``` - @@ -1216,42 +1153,41 @@ A detailed explanation of this solution can be found in [the example's `README` import { Prisma, PrismaClient } from "@prisma/client"; function bypassRLS() { -return Prisma.defineExtension((prisma) => -prisma.$extends({ + return Prisma.defineExtension((prisma) => + prisma.$extends({ query: { $allModels: { async $allOperations({ args, query }) { const [, result] = await prisma.$transaction([ -prisma.$executeRaw`SELECT set_config('app.bypass_rls', 'on', TRUE)`, -query(args), -]); -return result; -}, -}, -}, -}) -); + prisma.$executeRaw`SELECT set_config('app.bypass_rls', 'on', TRUE)`, + query(args), + ]); + return result; + }, + }, + }, + }) + ); } function forCompany(companyId: string) { -return Prisma.defineExtension((prisma) => -prisma.$extends({ + return Prisma.defineExtension((prisma) => + prisma.$extends({ query: { $allModels: { async $allOperations({ args, query }) { const [, result] = await prisma.$transaction([ -prisma.$executeRaw`SELECT set_config('app.current_company_id', ${companyId}, TRUE)`, -query(args), -]); -return result; -}, -}, -}, -}) -); + prisma.$executeRaw`SELECT set_config('app.current_company_id', ${companyId}, TRUE)`, + query(args), + ]); + return result; + }, + }, + }, + }) + ); } - -```` +``` ```typescript const prisma = new PrismaClient(); const user = await prisma.$extends(bypassRLS()).user.findFirstOrThrow(); @@ -1269,8 +1205,7 @@ const projects = await companyPrisma.project.findMany({ }); invariant(projects.every((project) => project.companyId === user.companyId)); -```` - +``` ```prisma model Company { id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid @@ -1322,7 +1257,6 @@ model Task { assignee User? @relation(fields: [userId], references: [id], onDelete: SetNull) } ``` - ```sql -- Enable Row Level Security ALTER TABLE "Company" ENABLE ROW LEVEL SECURITY; @@ -1348,7 +1282,6 @@ CREATE POLICY bypass_rls_policy ON "User" USING (current_setting('app.bypass_rls CREATE POLICY bypass_rls_policy ON "Project" USING (current_setting('app.bypass_rls', TRUE)::text = 'on'); CREATE POLICY bypass_rls_policy ON "Task" USING (current_setting('app.bypass_rls', TRUE)::text = 'on'); ``` - diff --git a/apps/blog/content/blog/cloud-connectivity-report-2024/index.mdx b/apps/blog/content/blog/cloud-connectivity-report-2024/index.mdx index 6d40e1e36e..41f48400d4 100644 --- a/apps/blog/content/blog/cloud-connectivity-report-2024/index.mdx +++ b/apps/blog/content/blog/cloud-connectivity-report-2024/index.mdx @@ -14,12 +14,11 @@ The Prisma Cloud Connectivity Report 2024 analyzes network latency between AWS r At Prisma, we are operating services in most AWS regions and all Cloudflare PoPs outside of China. As a result, we have an extensive set of latency data for requests between Cloudflare and AWS. This question on X prompted us to start releasing this data in a yearly Cloud Connectivity Report: - + We collect p50, p90, p95, p99 and p999 latency metrics for the intersection of 16 AWS regions and 266 Cloudflare points of presence (PoPs) for a total of 21k data points. This report will focus on a few key data points, and the full dataset is linked at the end. ## AWS regions with the fastest connectivity to Cloudflare - The three AWS regions with the lowest latency to a nearby Cloudflare PoP are all in Asia Pacific: ![](/cloud-connectivity-report-2024/imgs/8eb86e862d6739302c7bc6209ef01b2f157146f3-1480x302.png) diff --git a/apps/blog/content/blog/cloudflare-partnership-qerefgvwirjq/index.mdx b/apps/blog/content/blog/cloudflare-partnership-qerefgvwirjq/index.mdx index 920651bb52..eb1ad9efb4 100644 --- a/apps/blog/content/blog/cloudflare-partnership-qerefgvwirjq/index.mdx +++ b/apps/blog/content/blog/cloudflare-partnership-qerefgvwirjq/index.mdx @@ -53,3 +53,4 @@ This partnership further underscores Cloudflare's commitment to spearheading tec The remarkable response to Accelerate has left us thrilled. Inspired by this, our vision for [Data DX](https://datadx.io) will drive us to introduce more exciting products in this category. Don’t miss out on our upcoming product releases; [subscribe now](https://www.prisma.io/blog) and be the first to know when we share more news! We want to express our gratitude to our community for your invaluable feedback and collaboration. Your inputs have shaped our Data DX innovations, and we appreciate your support as we continue to unveil future products. There’s a lot more that we have under wraps that we are excited to bring to you over the coming months. Your support makes you the best community any technology company could wish for. Thank you from the bottom of our hearts! + diff --git a/apps/blog/content/blog/cloudflare-unikernels-and-bare-metal-life-of-a-prisma-postgres-query/index.mdx b/apps/blog/content/blog/cloudflare-unikernels-and-bare-metal-life-of-a-prisma-postgres-query/index.mdx index 498c10d465..ee85a0080a 100644 --- a/apps/blog/content/blog/cloudflare-unikernels-and-bare-metal-life-of-a-prisma-postgres-query/index.mdx +++ b/apps/blog/content/blog/cloudflare-unikernels-and-bare-metal-life-of-a-prisma-postgres-query/index.mdx @@ -48,11 +48,11 @@ In the next sections, we'll take a closer look at each stage and explain what's ### Stage 1: It all starts with Prisma ORM -[Prisma ORM](https://www.prisma.io/orm) is where the journey of a Prisma Postgres query naturally starts. +[Prisma ORM](https://www.prisma.io/orm) is where the journey of a Prisma Postgres query naturally starts. #### No query engine needed on the application server with Prisma Postgres -If you've used Prisma ORM in the past, you may be aware that it uses a _Query Engine_ that's implemented in Rust and which runs as a binary on your application server. +If you've used Prisma ORM in the past, you may be aware that it uses a _Query Engine_ that's implemented in Rust and which runs as a binary on your application server. > Note that while we're still talking about the Query Engine being written in Rust in this context, it is currently being rewritten in TypeScript. Learn more [here](https://t.co/JJnKm2Fs7y). @@ -75,16 +75,15 @@ const users = await prisma.post.findMany({ cacheStrategy: { ttl: 60, swr: 30, - }, -}); + } +}) ``` - This query fetches all published posts from the database and additionally specifies two parameters that will be relevant for the Prisma Postgres cache: -- [Time-To-Live](https://www.prisma.io/docs/accelerate/caching#time-to-live-ttl) (`ttl`): Determines how long cached data is considered _fresh_. When you set a TTL value, Prisma Postgres will serve the cached data for that duration without querying the database. +- [Time-To-Live](https://www.prisma.io/docs/accelerate/caching#time-to-live-ttl) (`ttl`): Determines how long cached data is considered _fresh_. When you set a TTL value, Prisma Postgres will serve the cached data for that duration without querying the database. - [Stale-While-Revalidate](https://www.prisma.io/docs/accelerate/caching#stale-while-revalidate-swr) (`swr`): Allows Prisma Postgres to serve _stale_ cached data while fetching fresh data in the background. When you set an SWR value, Prisma Postgres will continue to serve the cached data for that duration, even if it's past the TTL, while simultaneously updating the cache with new data from the database. -In this example, the data will be considered fresh for 30 seconds (TTL). After that, for the next 60 seconds (SWR), Prisma Postgres's cache will serve the stale data while fetching fresh data in the background. +In this example, the data will be considered fresh for 30 seconds (TTL). After that, for the next 60 seconds (SWR), Prisma Postgres's cache will serve the stale data while fetching fresh data in the background. Prisma Postgres serves cached data from an edge location close to your application. If you deploy your app to multiple locations, this global cache can dramatically improve the performance of your app! @@ -108,20 +107,17 @@ So, when the query above is executed in an application, what happens next? Becau } } ``` - -Note that the `selection` argument specifies the fields that should be fetched from the database. Since we didn't use a `select` or `include` option on our query, its value simply is `"$scalars": true` which means that all scalar fields of the target model will be return from the database. +Note that the `selection` argument specifies the fields that should be fetched from the database. Since we didn't use a `select` or `include` option on our query, its value simply is `"$scalars": true` which means that all scalar fields of the target model will be return from the database. The next step for the request to be evaluated by Prisma Postgres' authentication and cache layers. ### Stage 2: Authenticating the request Before hitting the cache to check if the query result can be served from there, the query needs to be authenticated. A Prisma Postgres URL always contains an `apiKey` argument that encodes the user credentials: - ``` prisma+postgres://accelerate.prisma-data.net/?api_key=ey... ``` - -The auth layer is implemented using Cloudflare Workers, and therefore in close physical proximity to the origin of the query. It uses the `apiKey` value to identify the user, validate the access permissions and route the request to the next stage. +The auth layer is implemented using Cloudflare Workers, and therefore in close physical proximity to the origin of the query. It uses the `apiKey` value to identify the user, validate the access permissions and route the request to the next stage. ### Stage 3: To cache or not to cache @@ -136,9 +132,9 @@ The main purpose of this routing layer is to determine whether the Prisma Postgr So, let's investigate the path through Prisma Postgres' caching layer. -Being built on top of Cloudflare Workers, the Prisma Postgres cache takes advantage of the official [Cloudflare Cache API](https://developers.cloudflare.com/workers/runtime-apis/cache/). +Being built on top of Cloudflare Workers, the Prisma Postgres cache takes advantage of the official [Cloudflare Cache API](https://developers.cloudflare.com/workers/runtime-apis/cache/). -As a cache key, it uses a hash that's computed based on the _entire_ Prisma ORM query (including values for the query parameters, such as `published: true` in the case above). This approach errs on the side of cache misses and only returns data from the cache if there's a 100% certainty that this exact query was sent to the database and that its result was cached before. +As a cache key, it uses a hash that's computed based on the _entire_ Prisma ORM query (including values for the query parameters, such as `published: true` in the case above). This approach errs on the side of cache misses and only returns data from the cache if there's a 100% certainty that this exact query was sent to the database and that its result was cached before. You can see the statistics of your caching behavior in the Prisma Postgres dashboard: @@ -158,7 +154,7 @@ These VMs host the Query Engine that was moved out of the application server (as ### Stage 5: Entering the unikernel database -Prisma Postgres is based on *unikernels* (think: "hyper-specialized operating systems") running as ultra-lightweight microVMs on our own bare metal servers. +Prisma Postgres is based on _unikernels_ (think: "hyper-specialized operating systems") running as ultra-lightweight microVMs on our own bare metal servers. > Check out the [Early Access announcement](https://www.prisma.io/blog/announcing-prisma-postgres-early-access#building-a-managed-postgresql-service-on-millisecond-cloud-infrastructure) to learn about the details of that architecture, our collaboration with [Unikraft](https://unikraft.cloud/), and the millisecond cloud stack that enables the performance benefits of Prisma Postgres. @@ -176,7 +172,7 @@ Thanks to this highly efficient stack, a database instance incurs costs only whe Back to our query: After the initial JSON representation of the query has been transformed into an efficient SQL statement, the query finally reaches the database layer via TCP. The PostgreSQL instances are deployed using Unikraft's millisecond cloud stack on our own bare metal servers in close proximity to the connection pool. -The first stop here is the [Unikraft proxy](https://unikraft.cloud/how-it-works/#control-plane) which is responsible for maintaining the TCP connections to the connection pool. +The first stop here is the [Unikraft proxy](https://unikraft.cloud/how-it-works/#control-plane) which is responsible for maintaining the TCP connections to the connection pool. The proxy now talks to the Unikraft controller which is responsible for managing the _actual_ Prisma Postgres instances. At this point, there are two possible states: @@ -187,13 +183,13 @@ Don't get confused by the "pause" and "wake up" terminology here. Thanks to the ## What's next for the Prisma Postgres architecture? -While we've seen a lot of excitement about the current technology stack and the benefits it provides to developers already, we are not going to stop here! +While we've seen a lot of excitement about the current technology stack and the benefits it provides to developers already, we are not going to stop here! There are a number of additional optimizations that we see possible in future iterations of Prisma Postgres, most notably: We are going to move the connection pool onto the _same machines_ that are running the Prisma Postgres instances: ![Prisma Postgres components](/cloudflare-unikernels-and-bare-metal-life-of-a-prisma-postgres-query/imgs/b38aa9235dab3c4bbef178b0944da6f97a9c2c2f-660x852.png) -The TCP connection is the most expensive part in the entire stack, due to the three-way handshake that needs to be done every time a connection is established. By reducing this TCP connection to a merely local one happening between two processes on the same machine, the latency caused by the physical distance between the connection pool and database instance would become entirely negligible. +The TCP connection is the most expensive part in the entire stack, due to the three-way handshake that needs to be done every time a connection is established. By reducing this TCP connection to a merely local one happening between two processes on the same machine, the latency caused by the physical distance between the connection pool and database instance would become entirely negligible. This is the core advantage of Prisma Postgres compared to other providers that are based on AWS (or another cloud provider's) infrastructure: When using a cloud provider, there's no guarantee that the connection pool and database instance are running on the same host but there's always going to be a network hop. @@ -202,7 +198,6 @@ This is the core advantage of Prisma Postgres compared to other providers that a In this article, we looked under the covers of Prisma Postgres and the next-generation technology stack it's built on. If you are already using Prisma ORM, give Prisma Postgres a try [by importing the data from your existing database](https://www.prisma.io/docs/getting-started/prisma-postgres/import-from-existing-database) in . Otherwise, try out Prisma Postgres from scratch by running this command in your terminal: - ``` npx prisma@latest init --db -``` +``` \ No newline at end of file diff --git a/apps/blog/content/blog/cockroach-ga-5JrD9XVWQDYL/index.mdx b/apps/blog/content/blog/cockroach-ga-5JrD9XVWQDYL/index.mdx index 8829160392..9b07080e30 100644 --- a/apps/blog/content/blog/cockroach-ga-5JrD9XVWQDYL/index.mdx +++ b/apps/blog/content/blog/cockroach-ga-5JrD9XVWQDYL/index.mdx @@ -16,8 +16,8 @@ tags: --- On May 10th, we were thrilled to release version [3.14.0](https://github.com/prisma/prisma/releases/tag/3.14.0) of -Prisma ORM, which brought [CockroachDB](https://www.cockroachlabs.com/product/) support to GA! This production-ready -feature allows developers to make use of a scalable and resilient database. + Prisma ORM, which brought [CockroachDB](https://www.cockroachlabs.com/product/) support to GA! This production-ready + feature allows developers to make use of a scalable and resilient database. ## CockroachDB support in Prisma is now Generally Available 💙 @@ -66,13 +66,11 @@ model User { + age Int } ``` - Then create a new migration to account for that change. ```shell npx prisma migrate dev --name add-age ``` - ![](/cockroach-ga-5JrD9XVWQDYL/imgs/migration.png) Finally, ideally during a CI/CD step, the changes can be deployed to the database and CockroachDB will apply these across all of the databases in the cluster without downtime. @@ -80,7 +78,6 @@ Finally, ideally during a CI/CD step, the changes can be deployed to the databas ```shell npx prisma migrate deploy ``` - ## Effectively optimize your queries On top of the performance and scaling benefits of a distributed serverless database, Prisma allows developers to fine-tune their database to fit the querying needs of their applications. @@ -100,8 +97,7 @@ To jump in and begin building with CockroachDB and Prisma, you can use [Prisma M To get started with CockroachDB and Prisma, you can follow our guide to set up a new project from scratch.
-[Start from scratch with -CockroachDB](https://www.prisma.io/docs/getting-started/setup-prisma/start-from-scratch/relational-databases/connect-your-database-typescript-cockroachdb) +[Start from scratch with CockroachDB](https://www.prisma.io/docs/getting-started/setup-prisma/start-from-scratch/relational-databases/connect-your-database-typescript-cockroachdb) ### ... or use Prisma with your existing CockroachDB database @@ -110,5 +106,4 @@ If you already have an existing project that uses a CockroachDB database, you ca Prisma's _introspection_ features reads the schema of your database and automatically builds the Prisma schema with those models.
-[Add Prisma to existing CockroachDB -project](https://www.prisma.io/docs/getting-started/setup-prisma/add-to-existing-project/relational-databases-typescript-cockroachdb) +[Add Prisma to existing CockroachDB project](https://www.prisma.io/docs/getting-started/setup-prisma/add-to-existing-project/relational-databases-typescript-cockroachdb) diff --git a/apps/blog/content/blog/compliance-reqs-complete/index.mdx b/apps/blog/content/blog/compliance-reqs-complete/index.mdx index 9b527df18c..de58286d91 100644 --- a/apps/blog/content/blog/compliance-reqs-complete/index.mdx +++ b/apps/blog/content/blog/compliance-reqs-complete/index.mdx @@ -34,7 +34,7 @@ The General Data Protection Regulation (GDPR) is a comprehensive data protection ### ISO 27001 -ISO 27001 is an international standard for information security management systems (ISMS). By completing all the required steps for this comprehensive standard, our customers are ensured that Prisma has implemented a systematic approach to managing sensitive company and customer information. This includes risk management, ensuring data integrity, and protecting against unauthorized access. +ISO 27001 is an international standard for information security management systems (ISMS). By completing all the required steps for this comprehensive standard, our customers are ensured that Prisma has implemented a systematic approach to managing sensitive company and customer information. This includes risk management, ensuring data integrity, and protecting against unauthorized access. ### The Value of Compliance for Our Customers @@ -46,6 +46,6 @@ ISO 27001 is an international standard for information security management syste ### Commitment to Ongoing Compliance -Those who understand the compliance process and have gone through it know that this not a one-time effort but a continuous process of improvement. We are committed to regularly reviewing and enhancing our security measures to stay ahead of potential threats and comply with evolving regulations. Our dedication to completing and maintaining the requirements for SOC2 Type II, HIPAA, GDPR, and ISO 27001:2022 certifications reflects our promise to provide secure, reliable, and trustworthy solutions for our customers. +Those who understand the compliance process and have gone through it know that this not a one-time effort but a continuous process of improvement. We are committed to regularly reviewing and enhancing our security measures to stay ahead of potential threats and comply with evolving regulations. Our dedication to completing and maintaining the requirements for SOC2 Type II, HIPAA, GDPR, and ISO 27001:2022 certifications reflects our promise to provide secure, reliable, and trustworthy solutions for our customers. For more information about our compliance journey and how Prisma can help you achieve your data security goals, feel free to visit our [Trust Center](https://trust.prisma.io/) or contact us at [compliance@prisma.io](mailto:compliance@prisma.io). diff --git a/apps/blog/content/blog/connect-your-apps-to-prisma-postgres-via-vercel-marketplace-integration/index.mdx b/apps/blog/content/blog/connect-your-apps-to-prisma-postgres-via-vercel-marketplace-integration/index.mdx index 8b4bb11e33..9837651d94 100644 --- a/apps/blog/content/blog/connect-your-apps-to-prisma-postgres-via-vercel-marketplace-integration/index.mdx +++ b/apps/blog/content/blog/connect-your-apps-to-prisma-postgres-via-vercel-marketplace-integration/index.mdx @@ -34,7 +34,7 @@ With the new Prisma Postgres integration, you can: ## Deploy our Next.js starter template now -To get you started, we have built a [starter template](https://vercel.com/templates/next.js/prisma-postgres) for you that can deploy with a single-click. +To get you started, we have built a [starter template](https://vercel.com/templates/next.js/prisma-postgres) for you that can deploy with a single-click. ![](/connect-your-apps-to-prisma-postgres-via-vercel-marketplace-integration/imgs/37841e4d757e5ec1c09e6285933fc7ec16ad1902-3248x2112.png) diff --git a/apps/blog/content/blog/connections-edges-nodes-in-relay-758d358aa4c7/index.mdx b/apps/blog/content/blog/connections-edges-nodes-in-relay-758d358aa4c7/index.mdx index ce7469d137..125dba10c4 100644 --- a/apps/blog/content/blog/connections-edges-nodes-in-relay-758d358aa4c7/index.mdx +++ b/apps/blog/content/blog/connections-edges-nodes-in-relay-758d358aa4c7/index.mdx @@ -11,7 +11,7 @@ heroImagePath: "/connections-edges-nodes-in-relay-758d358aa4c7/imgs/hero-a86c8de --- The terminology of Relay can be quite overwhelming in the beginning. Relay introduces a handful of new concepts on top -of GraphQL, mainly in order to manage relationships between models.. + of GraphQL, mainly in order to manage relationships between models.. This already leads to the first new term: a one-to-many relationship between two models is called a **connection**. @@ -29,7 +29,6 @@ Let’s consider this following simple GraphQL query. It fetches the `releaseDat } } ``` - Now let’s take this query and adjust it to the expected format of Relay. ```graphql @@ -45,7 +44,6 @@ Now let’s take this query and adjust it to the expected format of Relay. } } ``` - ## Edges and nodes Okay, let’s see what’s going on here. The `actors` connection now has a more complex structure containing the fields `edges` and `node`. These terms should be a bit more clear when looking at the following image. @@ -59,3 +57,4 @@ Lastly, we also notice the first: 10 parameter on the actors field. This gives u ## Further reading This was just a brief overview on connections in Relay. If you want to dive deeper please check out the [Relay docs on connections](https://relay.dev/docs/api-reference/store/#connectionhandler) or explore the [Relay Cursor Connections Specification](https://relay.dev/graphql/connections.htm). + diff --git a/apps/blog/content/blog/convergence/index.mdx b/apps/blog/content/blog/convergence/index.mdx index fc99f69695..7a27f2dadb 100644 --- a/apps/blog/content/blog/convergence/index.mdx +++ b/apps/blog/content/blog/convergence/index.mdx @@ -16,18 +16,16 @@ Drizzle's relational API v2 adds migrations, object-based queries, and abstracti A few years ago, Drizzle ORM burst onto the scene with a compelling pitch: at "~7.4kb minified+gzipped," they positioned themselves as the anti-Prisma. No heavy Rust engine, no migration complexity, no abstractions hiding SQL. Just lightweight, typesafe SQL that stays close to the metal. And honestly they had a point. The "just write SQL" philosophy is genuinely appealing, especially to developers who know databases well. We've been called "bloated," "over-engineered," and "too magical" enough times to appreciate why a fresh, minimal approach resonated with developers. Fast forward to 2025. Drizzle's "Relational API v2" and their massive "Alternation Engine" [PR #4439](https://github.com/drizzle-team/drizzle-orm/pull/4439) tell a different story. As of this writing, this new branch has 363 commits, [9,000+ tests](https://github.com/drizzle-team/drizzle-orm/issues/2061#issuecomment-3239454117), a systematic implementation of migrations, object-based queries, and abstractions. As someone who's been through that exact journey (we remember when our migration test suite exploded), we can say with authority: this is impressive, necessary engineering work. -Here's what makes this really cool: two ORMs with opposite starting philosophies, built by different teams, are converging on remarkably similar thinking and arch. This isn't about who copied whom, it's about discovering what production applications actually need, and then doing the necessary to serve your the needs of your users. It's also about what this convergence reveals: certain patterns aren't arbitrary preferences made by engineering teams or biased framework opinions, they're optimal solutions that emerge when you tackle production-scale problems in complicated domains like databases. +Here's what makes this really cool: two ORMs with opposite starting philosophies, built by different teams, are converging on remarkably similar thinking and arch. This isn't about who copied whom, it's about discovering what production applications actually need, and then doing the necessary to serve your the needs of your users. It's also about what this convergence reveals: certain patterns aren't arbitrary preferences made by engineering teams or biased framework opinions, they're optimal solutions that emerge when you tackle production-scale problems in complicated domains like databases. Whether you start lightweight or comprehensive, you eventually end up in a similar place and we think that's pretty cool. And we'd be lying if we said it doesn't feel validating. 😉 ## The Migration Engine: Complexity Demands Specialized Attention - Let's talk about migrations. Drizzle has always offered schema-first migrations and automated tooling, and their initialk commands for the same have been remarkably similar to Prisma's from day one. But their ongoing Alternation Engine work now reveals something we learned the hard way back in 2021: building reliable migrations across databases isn't a weekend project, rather it's a sustained, core engineering effort. The test suite expansion alone tells the unavoidble story. You don't go from 600 to over 9,000 tests because you're over-engineering. You do it because migrations are genuinely complex: handling edge cases across database vendors, tricky alterations (column renames, type changes), data preservation during schema evolution, and the intricacies of multi-dialect support safely. ### Migration Commands: Parallel Evolution Validates Design - ```shell # Prisma's approach (since 2021) npx prisma db push # Push schema state to database @@ -41,20 +39,17 @@ npx drizzle-kit pull # Pull database state to schema (introspection) npx drizzle-kit migrate # Apply migrations npx drizzle-kit generate # Generate migrations ``` - The reason that these command structures have been remarkably similar from the start is simple: when you're building production-grade migration tooling that needs introspection, code generation, schema diffing, and migration execution across multiple database vendors, you will naturally arrive at similar command structures. Both teams independently discovered that separating rapid prototyping (push/pull) from stable production workflows (generate/migrate) isn't an arbitrary choice; it's a necessary operational pattern. This parallel evolution proves that these core design patterns were never over-engineering. They are simply what production migration systems mandate. We learned this through years of difficult iteration. Drizzle is learning it now through their comprehensive rewrite. It’s the same lessons, just on a different timeline. ### What The Rewrite Reveals - ```shell PR #4439 - "Alternation Engine" - 363 commits (to date) -- ~167k lines added, 67k removed +- ~167k lines added, 67k removed - Test suite: 600 → 9k+ tests ``` - Here's what we truly respect about the Alternation Engine work: it shows a team that's willing to execute a massive rebuild when users need more. The scope of this endeavor, refining diffing, restructuring folders, adding CockroachDB and SQL Server support, is the exact gritty technical road we traveled with Prisma Migrate. It's not glamorous work, but it's undeniably essential. The commitment to 9,000 tests isn't just about completeness. They’re irrefutable proof that getting migrations right across different databases is genuinely difficult. You can't hand-wave edge cases. You simply cannot ignore the intricacies of ALTER TABLE across vendors. Production usage exposes every single corner case. @@ -64,18 +59,16 @@ To be clear: Drizzle's SQL-first philosophy has genuine, specific merit. For tea But when you scale to production complexity, need to coordinate schema changes across multiple teams, maintaining reliability across environments, and moving fast without breaking things, you inevitably end up building what they're building now: sophisticated diffing, extensive testing, and robust tooling. The [9k tests](https://github.com/drizzle-team/drizzle-orm/issues/2061#issuecomment-3239454117) aren't overkill. They are the mandated cost of doing migrations right at scale. ## Relational Queries: The Complete Convergence - Okay, this is where it gets technically interesting. Let's talk about relational queries. In their own [Relational API v2 discussion](https://github.com/drizzle-team/drizzle-orm/discussions/2316), Drizzle's team made an interesting admission: they acknowledged that their original approach had "several major flaws" and concluded that object-based filters, the exact pattern their early positioning dismissed as unnecessary abstraction, now "just feels natural." -We don't say this to gloat or score cheap points. We say it because watching another team arrive at the same conclusion through independent experience is genuinely validating. It confirms that this design choice is simply superior for developer experience at scale. +We don't say this to gloat or score cheap points. We say it because watching another team arrive at the same conclusion through independent experience is genuinely validating. It confirms that this design choice is simply superior for developer experience at scale. Let's look at the APIs side by side. ### Fetching Related Data - Spot the Difference - ```tsx // Let's look at a real-world query: fetching published -// posts with author details and recent comments. +// posts with author details and recent comments. // The similarity is remarkable: // Prisma (since 2020) @@ -85,18 +78,18 @@ const postsWithDetails = await prisma.post.findMany({ author: { select: { name: true, - email: true, - }, + email: true + } }, comments: { where: { approved: true }, include: { - user: true, + user: true }, - orderBy: { createdAt: "desc" }, - take: 5, - }, - }, + orderBy: { createdAt: 'desc' }, + take: 5 + } + } }); // Drizzle's Relational Queries v2 (2025) @@ -106,21 +99,20 @@ const postsWithDetails = await db.query.posts.findMany({ author: { columns: { name: true, - email: true, - }, + email: true + } }, comments: { where: { approved: true }, with: { - user: true, + user: true }, - orderBy: { createdAt: "desc" }, - limit: 5, - }, - }, + orderBy: { createdAt: 'desc' }, + limit: 5 + } + } }); ``` - The APIs are nearly identical: | Prisma | Drizzle Relational API v2 | | --- | --- | @@ -134,8 +126,8 @@ When we launched this API in 2020, people loudly said we were over-abstracting S Here's the undeniable thing folks: we're not claiming they copied us (though we'd be flattered). We're claiming that when you build for production-scale relational queries, you mujst naturally converge on these patterns. They feel natural because they are fundamentally superior for developer experience and correctnes. -The unavoidable takeaway comes stratight from Drizzle's [v2 migration documentation](https://rqbv2.drizzle-orm-fe.pages.dev/docs/relations-v1-v2), which lists the features conspicuously "not supported in v1": +The unavoidable takeaway comes stratight from Drizzle's [v2 migration documentation](https://rqbv2.drizzle-orm-fe.pages.dev/docs/relations-v1-v2), which lists the features conspicuously "not supported in v1": - Filtering by relations - Using `offset` on nested relations - Predefined filters in relation definitions @@ -147,10 +139,9 @@ Every single one of these items has been a core, fundamental Prisma feature sinc Another final piece of evidence is structural: Drizzle RQB v2 deprecated their callback-based syntax entirely, moving it to `db._query` and `import from 'drizzle-orm/_relations'` while giving the clean `db.query` namespace to object-based patterns. That underscore prefix is a universally understood signal JavaScript, conventionally marking code as "internal," "deprecated," or "legacy." Their [v2 migration docs](https://rqbv2.drizzle-orm-fe.pages.dev/docs/relations-v1-v2) confirm: "`where` is now object" and "`orderBy` is now object." This isn't incremental improvement. It's a philosophical pivot driven by what users actually need. The new default is object-based queries…the exact pattern Prisma pioneered. Consider this example: - ```sql -- Raw SQL equivalent: published posts with author + latest 5 approved comments -SELECT +SELECT p.id, p.title, p.content, u.name as author_name, u.email as author_email, COALESCE(c.comment_data, '[]'::json) AS comment_data @@ -168,36 +159,33 @@ LEFT JOIN LATERAL ( ) ORDER BY c.created_at DESC ) as comment_data FROM ( - SELECT * FROM comments - WHERE post_id = p.id AND approved = true - ORDER BY created_at DESC + SELECT * FROM comments + WHERE post_id = p.id AND approved = true + ORDER BY created_at DESC LIMIT 5 ) c INNER JOIN users cu ON c.user_id = cu.id ) c ON true WHERE p.published = true; ``` - ```tsx // Prisma: Same query, readable and maintainable const posts = await prisma.post.findMany({ where: { published: true }, include: { author: { - select: { name: true, email: true }, + select: { name: true, email: true } }, comments: { where: { approved: true }, include: { user: true }, - orderBy: { createdAt: "desc" }, - take: 5, - }, - }, + orderBy: { createdAt: 'desc' }, + take: 5 + } + } }); ``` - The readability difference is stark, and it's about maintenance overhead. The SQL demands LATERAL joins, JSON aggregation, deeply nested subqueries, and incredibly careful correlation. One wrong alias or forgotten JOIN condition and you're debugging for hours. The object-based version reads like a description of what you want. For simple queries? Sure, raw SQL is fine. But production applications need code you can reliably reason about at 3am, debug without a PhD in SQL optimization, and onboard junior developers to understand quickly. This operational necessity is precisely why both ORMs converged here. The "close to SQL" approach works great for straightforward queries and individual projects. But production-level complexity with distributed teams mandates higher-level patterns. Drizzle's users have perhaps learned this the hard way and are demanding a change. The Drizzle team listened and made the right call. That's good product development. - ```tsx *// Drizzle v1 - Manually navigate junction tables* const usersWithGroups = await db.query.users.findMany({ @@ -217,21 +205,17 @@ const mapped = usersWithGroups.map(user => ({ groups: user.usersToGroups.map(utg => utg.group) })); ``` - Having to manually navigate junction tables and map results yourself isn't "close to SQL," it's a maintenance nightmare. Drizzle RQB v2 finally added first-class many-to-many support, providing the seamless DX Prisma has always provided. Again, this isn't criticism: it's validation. The "just write SQL" approach always needs abstraction layers when real users build real applications that feature complex relationships and when team members have different skill levels. We learned this essential lesson in 2019. Drizzle learned it through direct user feedback. Same lesson, different cost. ## Performance: Everyone's Getting Better - Here's where the story gets truly interesting, and honestly, kind of fun. While Drizzle is forced to add sophisticated features (migrations, relational queries, growing test coverage), Prisma went the opposite direction in terms of architecture: we [removed our entire Rust engine](https://www.prisma.io/blog/rust-free-prisma-orm-is-ready-for-production), becoming 90% smaller and 3-4x faster in many scenarios. -Wait, smaller _and_ more features? Yes. Turns out the complexity isn't in the feature set, it's in the underlying architecture. We spent years building sophisticated query engines and migration tooling in Rust, only to discover we could achieve better results by optimizing our TypeScript implementation. TypeScript is making quite the waves when it comes to adoption, so like dutiful devs, we observe and adapt. Sometimes you need to build the complex version first specifically to learn what complexity you actually need to discard. Such is the way of the _Force_. +Wait, smaller *and* more features? Yes. Turns out the complexity isn't in the feature set, it's in the underlying architecture. We spent years building sophisticated query engines and migration tooling in Rust, only to discover we could achieve better results by optimizing our TypeScript implementation. TypeScript is making quite the waves when it comes to adoption, so like dutiful devs, we observe and adapt. Sometimes you need to build the complex version first specifically to learn what complexity you actually need to discard. Such is the way of the *Force*. ### Type-Checking Performance - There's another crucial dimension worth discussing: DX during actual coding. Our [comprehensive benchmarks](https://www.prisma.io/blog/why-prisma-orm-checks-types-faster-than-drizzle) with TypeScript expert David Blass already revealed something interesting about performance and Type overhead. But even more telling, Drizzle's own community raised concerns in their [v2 discussion](https://github.com/drizzle-team/drizzle-orm/discussions/2316), noting that "Prisma has a codegen step to create the types which would help the approach scale." That single comment cuts to the heart of the matter. While Drizzle relies on complex runtime inference from procedural TypeScript, Prisma uses a dedicated, declared schema ([PSL](https://www.prisma.io/blog/prisma-schema-language-the-best-way-to-define-your-data)) to generate a perfectly type-safe client. This code generation step, which Drizzle's early philosophy dismissed as "heavy," is the mandatory gateway to superior IntelliSense, predictable type safety, and efficient development when your schema reaches any non-trivial size. - ```shell // Type instantiations (lower is better) Schema Types: @@ -243,24 +227,21 @@ Schema Types: Queries: Prisma is ~1.5x faster than Drizzle Beta Prisma is ~2.1x faster than Drizzle Stable ``` - Why? Prisma generates types at build time while Drizzle infers them on every keystroke: - ```tsx // Prisma: Types precomputed during `prisma generate` // Result: .d.ts files with optimized types type User = { - id: number; - email: string; - name: string | null; - posts: Post[]; -}; + id: number + email: string + name: string | null + posts: Post[] +} // Drizzle: Types inferred from schema on every compile // Result: TypeScript does heavy lifting repeatedly -type User = InferSelectModel; // Complex inference chain +type User = InferSelectModel // Complex inference chain ``` - The point isn't "we win, they lose." The point is that different architectural choices have real tradeoffs. Code generation adds a build step but delivers [faster type-checking](https://benchmarks.prisma.io/?tab=type). Type inference is more immediate but can get expensive at scale. There's no free lunch, just different costs. ## What We're All Learning @@ -285,7 +266,6 @@ Step back and look at the unfolding pattern: two ORMs with opposite starting phi These patterns aren't "Prisma patterns," they're solutions that emerge when solving production database problems. We got here first. We'd argue our path was more direct. But the observed convergence validates tthe destination. ## Meanwhile, at Prisma - While the ecosystem converges on similar ORM patterns, we've been pushing in a different direction entirely. The Rust-to-TypeScript migration shows our commitment to DX over dogma. We built sophisticated Rust engines, learned what complexity we actually needed, then rebuilt in a way that's smaller and faster. That's what [Prisma v7](https://www.prisma.io/blog/announcing-prisma-orm-7-0-0) is all about, and we're proud of it. @@ -299,25 +279,21 @@ generator client { engineType = "client" } ``` - ```tsx // Step 2: Install adapter npm install @prisma/adapter-pg pg ``` - ```tsx // Step 3: Use it -import { PrismaClient } from "@prisma/client"; -import { PrismaPg } from "@prisma/adapter-pg"; +import { PrismaClient } from '@prisma/client' +import { PrismaPg } from '@prisma/adapter-pg' -const adapter = new PrismaPg(process.env.DATABASE_URL!); -const prisma = new PrismaClient({ adapter }); +const adapter = new PrismaPg(process.env.DATABASE_URL!) +const prisma = new PrismaClient({ adapter }) ``` - This is the kind of innovation that happens when you're not busy rebuilding features you once claimed weren't needed (ok, we admit, that was a little spicy). But seriously, we're excited about where this is going.) And here's the thing: we genuinely want other ORMs work great with Prisma Postgres too. A rising tide lifts all boats. More good ORMs means more developers building with databases, which means more potential Prisma Postgres users. Everyone wins. ## Looking Forward: Why Convergence Matters - The evolution from Drizzle v1 to v2 isn't just feature additions, it's a fundamental philosophical pivot validated through production experience. They deprecated callback-based queries, moved them to underscore-prefixed APIs, and gave the clean namespace to Prisma-like patterns. Their docs repeatedly note features that were "not supported in v1" but are now core to v2. We're flattered. We're validated. And honestly? We're impressed. Building a comprehensive relational query system and migration engine is genuinely hard work. The Drizzle team is doing it well. diff --git a/apps/blog/content/blog/coo-announcement-aer1fgviirjb/index.mdx b/apps/blog/content/blog/coo-announcement-aer1fgviirjb/index.mdx index e24a070403..ca5fb732c3 100644 --- a/apps/blog/content/blog/coo-announcement-aer1fgviirjb/index.mdx +++ b/apps/blog/content/blog/coo-announcement-aer1fgviirjb/index.mdx @@ -19,6 +19,7 @@ We’re excited to announce that [Nitin Gupta](https://linkedin.com/in/gniting) As COO, Nitin will oversee our commercial, people, and operational functions, focusing on alignment and optimization to support the delivery of our product ambitions. This is a pivotal role for Prisma as we continue to build upon the upward trajectory of our product adoption in the market. -“_Since our [last fundraise](https://www.prisma.io/blog/series-b-announcement-v8t12ksi6x), we’ve felt the need to augment our executive team, and Nitin brings the right mix of experience, foresight, and execution rigor. I’m excited to have him join Prisma and be my sparring partner._” Søren Bramer Schmidt, CEO @ Prisma. +“*Since our [last fundraise](https://www.prisma.io/blog/series-b-announcement-v8t12ksi6x), we’ve felt the need to augment our executive team, and Nitin brings the right mix of experience, foresight, and execution rigor. I’m excited to have him join Prisma and be my sparring partner.*” Søren Bramer Schmidt, CEO @ Prisma. We are thrilled to have Nitin on board and look forward to the contributions he will make as Prisma continues to grow and evolve. Welcome to the team, Nitin! + diff --git a/apps/blog/content/blog/data-platform-static-ips/index.mdx b/apps/blog/content/blog/data-platform-static-ips/index.mdx index c60551e423..94cc6e297a 100644 --- a/apps/blog/content/blog/data-platform-static-ips/index.mdx +++ b/apps/blog/content/blog/data-platform-static-ips/index.mdx @@ -15,11 +15,11 @@ tags: --- We're launching Early Access support for static egress IPs. Keep your databases secure by ensuring that the Prisma -Data Platform only connects to your database from specific IPs. [Try it out](https://cloud.prisma.io/) and share your -feedback. + Data Platform only connects to your database from specific IPs. [Try it out](https://cloud.prisma.io/) and share your + feedback. > ⚠️ **Outdated Information** -> +> > Please be aware that the information provided in this blog post is outdated. Since its publication, there have been updates to the Prisma Data Platform, including the discontinuation of the Data Proxy. > If you require connection pooling and global caching, we recommend exploring [Prisma Accelerate](https://www.prisma.io/data-platform/accelerate). > For the latest details on our platform's products and features, please visit our [website](https://www.prisma.io/) and consult our [changelog](https://www.prisma.io/changelog). @@ -66,6 +66,7 @@ You can enable static egress IPs for new and existing projects can use their dat > **Note:** IP addresses are specific to the region where the data proxy is configured. Changing the region of the Data Proxy will change the IPs for egress and will thus require a change of the IP allow list on your provider + ### Enabling static egress IPs You can enable static egress IPs per environment in both new and existing projects. @@ -101,7 +102,7 @@ To get a glimpse into our current priorities and upcoming features, check out ou ## Try the static egress IPs and share your feedback > ⚠️ **Outdated Information** -> +> > Please be aware that the information provided in this blog post is outdated. Since its publication, there have been updates to the Prisma Data Platform, including the discontinuation of Prisma Data Proxy. If you require connection pooling and global caching, we recommend exploring [**Prisma Accelerate**](https://www.prisma.io/data-platform/accelerate). For the latest details on our platform's products and features, please visit our [**website**](https://www.prisma.io/) and consult our [**changelog**](https://www.prisma.io/changelog). Since the [static egress IPs](https://cloud.prisma.io/login) is in [Early Access](https://www.prisma.io/docs/data-platform/about/releases#early-access), we don't recommend using it in production. diff --git a/apps/blog/content/blog/database-access-in-react-server-components-r2xgk9aztgdf/index.mdx b/apps/blog/content/blog/database-access-in-react-server-components-r2xgk9aztgdf/index.mdx index 58cbd932bf..c97d10d03e 100644 --- a/apps/blog/content/blog/database-access-in-react-server-components-r2xgk9aztgdf/index.mdx +++ b/apps/blog/content/blog/database-access-in-react-server-components-r2xgk9aztgdf/index.mdx @@ -93,26 +93,24 @@ function ArtistPage({ artistId }) { - ); + ) } ``` - ### Building a fast and consistent user experience To add data fetching logic to an API, we'd need to fetch all data at once and pass it down to the different components. This way, we can achieve a consistent user experience by rendering all components at once. So we would end up with something like this: ```jsx function ArtistPage({ artistId }) { - const data = fetchAllData(); + const data = fetchAllData() return ( - ); + ) } ``` - This approach is fast because we only need to make a single request to our API. However, we find that the code is now **harder to maintain**. The reason being that the UI components are directly tightly coupled to the API response. So if we make a change in our UI, we need to update the API accordingly and vice-versa. @@ -152,7 +150,6 @@ function ArtistPage ({ artistId }){ ) } ``` - This approach is not fast because our parent component's children only start fetching data *after* the parent makes a request, receives a response, and renders. So we end up having a waterfall of network requests, where network requests start one after the other, instead of all at once: @@ -168,21 +165,21 @@ So in our Spotify app example, this is what our components will look like: function ArtistPage({ artistId }) { // requests will not finish at the same time // nor at the same order - const details = fetchDetails(artistId).data; - const topTracks = fetchTopTracks(artistId).data; - const discography = fetchDiscography(artistId).data; + const details = fetchDetails(artistId).data + const topTracks = fetchTopTracks(artistId).data + const discography = fetchDiscography(artistId).data return ( - ); + ) } ``` - This pattern will result in inconsistent behavior because if all components start fetching data together, they don't necessarily finish simultaneously. That's because the data fetching process depends on the network connection, which can vary. So while now we have fast, easy-to-maintain code, we are sacrificing user experience. + So is it impossible to have all three? Not really. Facebook faced this challenge and already came up with a solution using [Relay](https://relay.dev/) and [GraphQL](https://graphql.org/) fragments. Relay manages the fragments and only sends a single request, avoiding the waterfall of network requests issue. @@ -234,7 +231,6 @@ cd react-server-components-demo npm install npm start ``` - The app will be running at [http://localhost:4000](http://localhost:4000) and this is what you'll see: ![Server Components demo screenshot](/database-access-in-react-server-components-r2xgk9aztgdf/imgs/server-components-demo.png) @@ -277,7 +273,6 @@ server-components-demo/ ┣ db.server.js ┗ index.client.js ``` - The `/notes` directory is where we save notes, in markdown format, when they're created on the frontend. The `/prisma` directory contains two files: @@ -303,7 +298,6 @@ model Note { body String? } ``` - The schema file is written in Prisma Schema Language (PSL). To get the best possible development experience, make sure you install our [VSCode extension,](https://marketplace.visualstudio.com/items?itemName=Prisma.prisma) which adds syntax highlighting, formatting, auto-completion, jump-to-definition, and linting for `.prisma` files. We specified that we're using SQLite and our `dev.db` file location in the `datasource` field. @@ -344,36 +338,34 @@ To create a note we created a `/notes` endpoint that handles `POST` requests. In ```js app.post( - "/notes", - handleErrors(async function (req, res) { + '/notes', + handleErrors(async function(req, res) { const result = await prisma.note.create({ data: { body: req.body.body, title: req.body.title, }, - }); + }) // ... // return newly created note's id // in the response object - sendResponse(req, res, result.id); + sendResponse(req, res, result.id) }), -); +) ``` - To get all notes, we created a `/notes` route and when we receive a `GET` request we will call and await the `findMany()` function to return all records inside the `notes` table in our database. ```js app.get( - "/notes", - handleErrors(async function (_req, res) { + '/notes', + handleErrors(async function(_req, res) { // return all records - const notes = await prisma.note.findMany(); - res.json(notes); + const notes = await prisma.note.findMany() + res.json(notes) }), -); +) ``` - A `GET` request to `/note/id` will return a single note when we pass its `id`. We get the note's `id` from the request's parameters using `req.param.id` and cast it to a number, since that's the type of the `id` we defined in our Prisma schema. @@ -382,25 +374,24 @@ We then use `findUnique` which returns a single record by a unique identifier. ```js app.get( - "/notes/:id", - handleErrors(async function (req, res) { + '/notes/:id', + handleErrors(async function(req, res) { const note = await prisma.note.findUnique({ where: { id: Number(req.params.id), }, - }); - res.json(note); + }) + res.json(note) }), -); +) ``` - Finally, to update a note, we can send a `PUT` requests to `/notes/:id` and we access the note's id from the request parameters. We then pass it to the `update()` function and pass the note's updates coming from the request's body. ```js app.put( - "/notes/:id", - handleErrors(async function (req, res) { - const updatedId = Number(req.params.id); + '/notes/:id', + handleErrors(async function(req, res) { + const updatedId = Number(req.params.id) await prisma.note.update({ where: { id: updatedId, @@ -409,30 +400,28 @@ app.put( title: req.body.title, body: req.body.body, }, - }); + }) // ... - sendResponse(req, res, null); + sendResponse(req, res, null) }), -); +) ``` - To delete a note, we send a `DELETE` request to `/notes/:id`. We then pass the note's id from the request parameters to the `delete` function. ```js app.delete( - "/notes/:id", - handleErrors(async function (req, res) { + '/notes/:id', + handleErrors(async function(req, res) { await prisma.note.delete({ where: { id: Number(req.params.id), }, - }); + }) // ... - sendResponse(req, res, null); + sendResponse(req, res, null) }), -); +) ``` - Note that all Prisma Client operations are promise-based, that's why we need to use async/await (or promises) when sending database queries using Prisma Client. ### A look at Server Components @@ -449,11 +438,10 @@ So in the `db.server.js` file, we're creating a new instance of Prisma Client. H ```js //db.server.js -import { PrismaClient } from "react-prisma"; +import { PrismaClient } from 'react-prisma' -export const prisma = new PrismaClient(); +export const prisma = new PrismaClient() ``` - In the `NoteList.server.js` component, we're importing `prisma` and the `SidebarNote` component, which is a regular React component that receives a note object as a prop. We're filtering the list of notes by making a query to the database using Prisma. @@ -462,8 +450,8 @@ We're retrieving all records inside the `notes` table, where the `title` of a no ```jsx // NoteList.server.js -import { prisma } from "./db.server"; -import SidebarNote from "./SidebarNote"; +import { prisma } from './db.server' +import SidebarNote from './SidebarNote' export default function NoteList({ searchText }) { const notes = prisma.note.findMany({ @@ -472,11 +460,11 @@ export default function NoteList({ searchText }) { contains: searchText ?? undefined, }, }, - }); + }) return notes.length > 0 ? (
    - {notes.map((note) => ( + {notes.map(note => (
  • @@ -484,14 +472,11 @@ export default function NoteList({ searchText }) {
) : (
- {searchText - ? `Couldn't find any notes titled "${searchText}".` - : "No notes created yet!"}{" "} + {searchText ? `Couldn't find any notes titled "${searchText}".` : 'No notes created yet!'}{' '}
- ); + ) } ``` - You'll notice that we don't need to `await` prisma here, that's because React uses a different mechanism that retries rendering when the data is cached. So it's still asynchronous, but you don't need to use async/await. ## Conclusion @@ -505,3 +490,4 @@ We also end up having a faster user experience since less JavaScript is shipped Finally, React's virtual DOM now spans the entire application instead of just the client. There are still many questions to be answered, and there are [drawbacks](https://github.com/josephsavona/rfcs/blob/server-components/text/0000-server-components.md#drawbacks), but it's exciting to see how the future of building Web apps using React might look like. + diff --git a/apps/blog/content/blog/database-access-on-the-edge-8F0t1s1BqOJE/index.mdx b/apps/blog/content/blog/database-access-on-the-edge-8F0t1s1BqOJE/index.mdx index 171d94bb10..c3c4bafd6b 100644 --- a/apps/blog/content/blog/database-access-on-the-edge-8F0t1s1BqOJE/index.mdx +++ b/apps/blog/content/blog/database-access-on-the-edge-8F0t1s1BqOJE/index.mdx @@ -15,6 +15,7 @@ tags: The Edge enables application deployment across the globe. This article explores what Edge environments are, the challenges that arise when working in Edge environments and how to access databases on the Edge using Prisma Accelerate. + ## What is the Edge? Traditionally, applications would be deployed to a single region or data center, in either a virtual machine, Platform as a Service (PaaS) like Heroku, or Functions as a Service (FaaS) like AWS Lambda. While this deployment pattern worked fine, the problem this created was that a user located on the other side of the globe would experience slightly longer response times. @@ -29,6 +30,7 @@ We took this a step further and introduced Edge computing such as [Vercel's Edge Edge computing works similarly to serverless functions, without the cold starts because they have a smaller runtime. This is great because web apps would perform better, but it comes at a cost: A smaller runtime on the Edge means that you don't have the exact same capabilities as you would have in regular Node.js runtime used in serverless functions. + ### Edge functions can easily exhaust database connections Edge functions are stateless, meaning they lack persistent state between requests. This architecture clashes with the stateful nature of traditional relational databases, where each request requires a new database connection. @@ -59,6 +61,7 @@ Here's a video from Jeff Delaney, [Fireship](https://fireship.io/), on whether " + ## Demo: Database access on the Edge Let's now take a look how to access a database from Vercel's Edge functions using Prisma Accelerate. @@ -90,7 +93,6 @@ model Quote { ### Prerequisites To successfully follow along, you will need: - - Node.js - A cloud-hosted [PostgreSQL](https://postgresql.org/) database ([set up a free PostgreSQL database on Supabase](https://dev.to/prisma/set-up-a-free-postgresql-database-on-supabase-to-use-with-prisma-3pk6) or on [Neon](https://neon.tech/)) - A [GitHub](https://github.com/) account to host your application code @@ -115,8 +117,8 @@ The page and API Route are also configured to use Vercel’s [Edge Runtime](http ```ts export const config = { - runtime: "experimental-edge", -}; + runtime: 'experimental-edge', +} ``` ### Set up the database @@ -174,19 +176,20 @@ git push -u origin main Once you’ve set up your repository, navigate to the [Platform Console](https://console.prisma.io/) and sign up for a free account if you don’t have one yet. + After signing up: 1. Create a new project by clicking the **New project** button - ![](/database-access-on-the-edge-8F0t1s1BqOJE/imgs/create-project.png) + ![](/database-access-on-the-edge-8F0t1s1BqOJE/imgs/create-project.png) 2. Fill out your **Project’s name** and then click the **Create Project** button - ![](/database-access-on-the-edge-8F0t1s1BqOJE/imgs/name-your-project.png) + ![](/database-access-on-the-edge-8F0t1s1BqOJE/imgs/name-your-project.png) 3. Enable Accelerate by clicking the **Enable Accelerate** button - ![](/database-access-on-the-edge-8F0t1s1BqOJE/imgs/project-dashboard.png) + ![](/database-access-on-the-edge-8F0t1s1BqOJE/imgs/project-dashboard.png) 4. Add your database connection string to the **Database connection string** field and select a region close to your database from the **Region** drop-down - ![](/database-access-on-the-edge-8F0t1s1BqOJE/imgs/add-database-url.png) + ![](/database-access-on-the-edge-8F0t1s1BqOJE/imgs/add-database-url.png) 5. Generate an Accelerate connection string by clicking the **Generate API key** button @@ -204,7 +207,7 @@ MIGRATE_DATABASE_URL="postgresql://USER:PASSWORD@HOST:PORT/DATABASE" ``` > The `MIGRATE_DATABASE_URL` variable will be used to apply any pending migrations during the build process. -> The `package.json` file uses the `vercel-build` hook script to run `prisma migrate deploy && next build` +The `package.json` file uses the `vercel-build` hook script to run `prisma migrate deploy && next build` Then install the [Prisma Accelerate client extension](https://www.npmjs.com/package/@prisma/extension-accelerate): diff --git a/apps/blog/content/blog/database-vs-application-demystifying-join-strategies/index.mdx b/apps/blog/content/blog/database-vs-application-demystifying-join-strategies/index.mdx index 4379cfc6c4..e19ac48052 100644 --- a/apps/blog/content/blog/database-vs-application-demystifying-join-strategies/index.mdx +++ b/apps/blog/content/blog/database-vs-application-demystifying-join-strategies/index.mdx @@ -50,24 +50,23 @@ As a developer, you're probably used to working with _nested_ objects, which loo } } ``` - In this example, the "object hierarchy" is as follows: `post` → `author` → `profile`. -This kind of nested structure is how data is represented in most programming languages that have the concept of an _object_. +This kind of nested structure is how data is represented in most programming languages that have the concept of an _object_. -However, if you've worked with a SQL database before, you're probably aware that related data is represented differently there, namely in a *flat* (or [_normalized_](https://en.wikipedia.org/wiki/Database_normalization)) way. With that approach, relations between entities are represented via *foreign keys* that specify _references_ across tables. +However, if you've worked with a SQL database before, you're probably aware that related data is represented differently there, namely in a _flat_ (or [_normalized_](https://en.wikipedia.org/wiki/Database_normalization)) way. With that approach, relations between entities are represented via _foreign keys_ that specify _references_ across tables. Here's a visual representation of the two approaches: ![](/database-vs-application-demystifying-join-strategies/imgs/47ca5a5f744cde64f8caaa359a2870df758d3c91-3200x1256.png) -This is a huge difference, not only in the way data is *physically* laid out on disk and in memory, but also when it comes to the *mental model* and to reasoning about the data. +This is a huge difference, not only in the way data is _physically_ laid out on disk and in memory, but also when it comes to the _mental model_ and to reasoning about the data. ### What does "joining" data mean? The process of joining data refers to getting the data from the _flat_ layout in a SQL database into a _nested_ structure that an application developer can use in their application. -This can happen in one of two places: +This can happen in one of two places: - In the **database**: A single SQL query is sent to the database. The query uses the `JOIN` keyword (or potentially a [correlated subquery](https://www.geeksforgeeks.org/sql-correlated-subqueries/)) to let the database perform the join across multiple tables and returns the nested structures. There are multiple ways of doing this join that we'll look at in the next section. - In the **application**: Multiple queries are sent to the database. Each query only accesses a single table and the query results are then joined in the application layer. @@ -76,7 +75,9 @@ Database-level joins have their benefits, but also some drawbacks if they're bec ## Three JOIN strategies: Naive, smart & application-level JOINs -At a high-level there are three different join strategies that can be applied, **"naive"** and **"smart"** JOINs on the DB-level, as well as **"application-level"** joins. Let's examine these one by one by use of the following schema: +At a high-level there are three different join strategies that can be applied, **"naive"** and **"smart"** JOINs on the DB-level, as well as **"application-level"** joins. Let's examine these one by one by use of the following schema: + + ```prisma model comments { @@ -104,7 +105,6 @@ model users { posts posts[] } ``` - ```sql CREATE TABLE users ( id SERIAL NOT NULL, @@ -132,6 +132,8 @@ CREATE INDEX idx_posts_author_id ON posts(author_id); CREATE INDEX idx_comments_post_id ON comments(post_id) ``` + + ### Naive DB-level JOINs lead to redundant data A naive DB-level JOIN refers to JOIN operations that don't take any additional measure for optimizations. These kinds of JOINs are often bad for performance for several reasons, let's explore! @@ -151,12 +153,11 @@ LEFT JOIN ORDER BY users.id, posts.id; ``` - The results returned by the database may look similar to this: ![](/database-vs-application-demystifying-join-strategies/imgs/aecb797ff9a4fec41786e1acfd8ea81be54ba073-404x571.png) -Do you notice something? There's _a lot_ of repetition in the data on the `user_name` column. +Do you notice something? There's _a lot_ of repetition in the data on the `user_name` column. Now, let's add the `comments` to the query: @@ -177,7 +178,6 @@ LEFT JOIN ORDER BY users.id, posts.id, comments.id ``` - Now that's even worse! Not only `user_name` repeats, but `post_title` does so as well: ![](/database-vs-application-demystifying-join-strategies/imgs/14d97834abcc4a48970178b9357c95b7c69773e1-1242x1694.png) @@ -186,8 +186,8 @@ The redundancy of the data has several negative implications: - Increased amount of (unnecessary) data that's sent over the wire, costing network bandwidth and increasing overall query latency. - The application layer needs to do additional work to arrive at the desired nested objects: - - deduplicate the redundant data - - re-construct the relationships between the data records + - deduplicate the redundant data + - re-construct the relationships between the data records Additionally, this kind of operation incurs a high CPU cost on the database, because it will query all three tables and perform its own in-memory mapping to join the data into one result set. @@ -203,9 +203,9 @@ In TypeScript, an example for this could look as follows (using a plain Postgres ```ts // Fetch data individually -const usersResult = await client.query("SELECT * FROM users"); -const postsResult = await client.query("SELECT * FROM posts"); -const commentsResult = await client.query("SELECT * FROM comments"); +const usersResult = await client.query('SELECT * FROM users'); +const postsResult = await client.query('SELECT * FROM posts'); +const commentsResult = await client.query('SELECT * FROM comments'); // Convert results to objects for easier processing const users = usersResult.rows; @@ -242,7 +242,6 @@ const joinedData = users.map((user) => { }; }); ``` - There are several benefits to this approach: - The database will generate a highly optimal execution plan for each of these queries and do virtually no CPU work since it's simply returning data from a single table. @@ -259,7 +258,7 @@ A major drawback, however, is that it requires multiple round trips to the datab Naive DB-level joins are almost never the best way to retrieve related data from your database, but does that mean your database should _never_ be responsible for joining data? Certainly not! -Database engines have become very powerful in the past years and constantly improved the ways how they optimize queries. In order to enable a database to generate the most optimal query plan, the most important thing is that it can understand the _intent_ of a query. +Database engines have become very powerful in the past years and constantly improved the ways how they optimize queries. In order to enable a database to generate the most optimal query plan, the most important thing is that it can understand the _intent_ of a query. There are two different factors to this: @@ -306,7 +305,6 @@ LEFT JOIN LATERAL ( GROUP BY u.id; ``` - Such a query produces the following results: ![](/database-vs-application-demystifying-join-strategies/imgs/ee53bcfbca9834771ff6856d9fcd6e77aef7bf04-1228x1708.png) @@ -320,11 +318,11 @@ While this query may yield better formatted results than the naive strategy, it ## The evolution of JOIN strategies in Prisma ORM -When Prisma ORM was [initially released in 2021](https://www.prisma.io/blog/prisma-the-complete-orm-inw24qjeawmb), it implemented the application-level join strategy for all its relation queries. +When Prisma ORM was [initially released in 2021](https://www.prisma.io/blog/prisma-the-complete-orm-inw24qjeawmb), it implemented the application-level join strategy for all its relation queries. -This strategy works really well when the application server and database are located closely to each other, helps with portability across database engines and increases scalability of the overall system (since application-layer CPU is easier and cheaper to scale than DB-level CPU). +This strategy works really well when the application server and database are located closely to each other, helps with portability across database engines and increases scalability of the overall system (since application-layer CPU is easier and cheaper to scale than DB-level CPU). -While the approach of application-level joins has served most developers well, it sometimes caused problems when application server and database couldn't be hosted closely to each other and the additional round trips negatively impacted overall query performance. +While the approach of application-level joins has served most developers well, it sometimes caused problems when application server and database couldn't be hosted closely to each other and the additional round trips negatively impacted overall query performance. That's why [we've added the smart DB-level joins as an alternative one year ago](https://www.prisma.io/blog/prisma-orm-now-lets-you-choose-the-best-join-strategy-preview), so developers have the option to always choose the most performant join strategy for their individual use case. @@ -334,9 +332,9 @@ Being able to use DB-level joins had been one of the [most popular feature reque ## Conclusion -Figuring out the most performant way to join data from multiple tables in a database is a complicated topic. In this article, we looked at three different approaches, _naive_ and _smart_ joins on the DB-level as well as _application-level_ joins. +Figuring out the most performant way to join data from multiple tables in a database is a complicated topic. In this article, we looked at three different approaches, _naive_ and _smart_ joins on the DB-level as well as _application-level_ joins. -Naive DB-level joins incur high CPU costs on the database server and lead to network overhead due to the unnecessary transfer of redundant data. +Naive DB-level joins incur high CPU costs on the database server and lead to network overhead due to the unnecessary transfer of redundant data. Application-level joins may be better suited for many scenarios due to their simplicity and cheap execution on the database-level. Systems using this strategy are also typically easier and less expensive to scale. diff --git a/apps/blog/content/blog/datadx-event-recap-z5pcp6hzbz5m/index.mdx b/apps/blog/content/blog/datadx-event-recap-z5pcp6hzbz5m/index.mdx index 617762df86..aeec6fe046 100644 --- a/apps/blog/content/blog/datadx-event-recap-z5pcp6hzbz5m/index.mdx +++ b/apps/blog/content/blog/datadx-event-recap-z5pcp6hzbz5m/index.mdx @@ -15,12 +15,11 @@ tags: Explore the insights from the [Discover Data DX virtual event](https://www.datadx.io/event) held on December 7th, 2023. The event brought together industry leaders to discuss the significance and principles of the emerging Data DX category. -Software development is now accessible to a wider audience, yet the data complexity when building applications has grown. These shifts highlight the need for simpler and more intuitive tools that empower developers to focus on the applications they want to build. +Software development is now accessible to a wider audience, yet the data complexity when building applications has grown. These shifts highlight the need for simpler and more intuitive tools that empower developers to focus on the applications they want to build. The new [Data DX](https://www.datadx.io/) category embodies this vision to simplify data-driven application development without sacrificing depth or functionality. ### Bringing the industry together - Prisma's Discover Data DX event showcased the role of Data DX as a unifying concept. It was inspiring to see varied companies embrace Data DX's principles. Together, they explored its meaning and how collaborative efforts could enhance the developer experience significantly. -## The evolution of database technologies +## The evolution of database technologies The first panel delved into how the developer experience with databases has evolved. Panelists from Prisma, Xata, PlanetScale, Snaplet, and Turso shared unique insights on enhancing the developer experience with databases. A central topic was distinguishing between necessary and accidental complexities in database management. The goal? **Simplify for developers** while recognizing some complexities are unavoidable. @@ -57,14 +56,12 @@ The panel also examined how to **balance powerful database capabilities and deve The discussion also covered emerging challenges in database management, such as schema changes, serverless technology, and AI/ML integration. - -
+
[Watch the panel](https://www.youtube.com/watch?v=6_y0CUehlUA&t=385s) **Panelists** - - [Deepthi Sigireddi](https://twitter.com/ATechGirl), Engineering Lead for Vitess at **PlanetScale** -- [Glauber Costa](https://twitter.com/glcst), Founder/CEO at **Turso** +- [Glauber Costa](https://twitter.com/glcst), Founder/CEO at **Turso** - [Peter Pistorius](https://twitter.com/appfactory), Founder at **Snaplet** - [Tudor Golubenco](https://twitter.com/tudor_g), CTO at **Xata** - [Søren Bramer Schmidt](https://twitter.com/sorenbs), CEO at **Prisma** @@ -72,8 +69,7 @@ The discussion also covered emerging challenges in database management, such as Moderated by [Petra Donka](https://twitter.com/petradonka), Head of Developer Connections at **Prisma** ## The future of building data-driven apps - -The second segment focused on the evolution of developer experience in data-driven application development, with representatives from Grafbase, Tinybird, RedwoodJS and Prisma sharing diverse perspectives. +The second segment focused on the evolution of developer experience in data-driven application development, with representatives from Grafbase, Tinybird, RedwoodJS and Prisma sharing diverse perspectives. ### The crucial role of developer experience @@ -97,12 +93,10 @@ The consensus was that modern tools should significantly **reduce time to market The discussion highlighted the gap in tooling quality between front-end and back-end development, especially in data management. Beyond pure scalability, the industry is now shifting to prioritize ease of use and developer experience in back-end and data tools. The panel also touched on AI's potential in application development, with Tinybird experimenting in this area, noting the need for human oversight. - -
+
[Watch the panel](https://www.youtube.com/watch?v=6_y0CUehlUA&t=3448s) **Panelists** - - [Fredrik Björk](https://twitter.com/fbjork), CEO at **Grafbase** - [Amy Dutton](https://twitter.com/selfteachme), Lead maintainer at **RedwoodJS** - [Alasdair Brown](https://github.com/sdairs), Head of DevRel at **Tinybird** @@ -110,8 +104,9 @@ The panel also touched on AI's potential in application development, with Tinybi Moderated by [Petra Donka](https://twitter.com/petradonka), Head of Developer Connections at **Prisma** -## Shaping the future -The Discover Data DX event was essential in establishing Data DX as an emerging category. It highlighted the industry alignment on the need for a more intuitive, efficient, and developer-focused approach to application development. +## Shaping the future +The Discover Data DX event was essential in establishing Data DX as an emerging category. It highlighted the industry alignment on the need for a more intuitive, efficient, and developer-focused approach to application development. Prisma will spearhead more initiatives to grow Data DX, driving innovation and collaboration in the industry. Learn more and stay updated at [datadx.io](https://www.datadx.io). + diff --git a/apps/blog/content/blog/datadx-manifesto-ikgyqj170k8h/index.mdx b/apps/blog/content/blog/datadx-manifesto-ikgyqj170k8h/index.mdx index 5f22e2317d..6613508725 100644 --- a/apps/blog/content/blog/datadx-manifesto-ikgyqj170k8h/index.mdx +++ b/apps/blog/content/blog/datadx-manifesto-ikgyqj170k8h/index.mdx @@ -17,14 +17,13 @@ Prisma presents the [Data DX manifesto](https://datadx.io), a transformative app ### Introducing the Data DX manifesto -The development landscape is ever-evolving, and with it, the challenges associated with data management, delivery, and usage. Recognizing the pressing need for a simple yet effective approach that addresses these challenges, we [introduced the Data DX concept](https://www.prisma.io/blog/cloudflare-partnership-qerefgvwirjq) when we announced our partnership with Cloudflare. +The development landscape is ever-evolving, and with it, the challenges associated with data management, delivery, and usage. Recognizing the pressing need for a simple yet effective approach that addresses these challenges, we [introduced the Data DX concept](https://www.prisma.io/blog/cloudflare-partnership-qerefgvwirjq) when we announced our partnership with Cloudflare. Today, we are following up on that announcement with the release of our complete thinking on the concept of the Data DX category in the form of the [Data DX Manifesto](https://datadx.io). The Data DX manifesto is a collaborative call to action, inviting developers and product creators to engage with this innovative philosophy. We wish for the community to start a dialogue and help guide and shape our thinking even further. The manifesto aims to: - - Outline benefits for developers who are building data-driven applications - Identify and recommend focus areas for products that fall into this new category - Act as a rallying cry to here @@ -37,6 +36,8 @@ For developers, it's about gaining a seamless and efficient pathway to handle da With the advent of Data DX, we aim to shed light on a prevalent challenge and propose a collective stride towards a solution. The manifesto serves as a starting point for a comprehensive discourse. + ### Embark on the Data DX Journey Whether you are a developer eager to adopt refined data handling methods or a creator aspiring to build products aligned with Data DX, a realm of possibilities awaits you at the [Manifesto website.](https://datadx.io) + diff --git a/apps/blog/content/blog/datadx-name-for-prismas-philosophy/index.mdx b/apps/blog/content/blog/datadx-name-for-prismas-philosophy/index.mdx index 6a51e79ff1..529004708a 100644 --- a/apps/blog/content/blog/datadx-name-for-prismas-philosophy/index.mdx +++ b/apps/blog/content/blog/datadx-name-for-prismas-philosophy/index.mdx @@ -15,26 +15,22 @@ Explore the evolution of [Data DX](https://www.datadx.io/) at Prisma, from its i In September 2023, Prisma launched a new category, Data DX, embodying a significant trend in the application development landscape. This initiative wasn't just about Prisma but signified a broader movement within the tech ecosystem. Data DX encapsulates the principles and practices aimed at enhancing the experience of developers working with data-driven applications. It's an approach that transcends specific tools or companies, embodying a philosophy that has been at the core of Prisma's operations since its early days under [Graphcool](https://graph.cool/). ## Our founder’s vision - Reflecting on his career shift from application development to building internal tooling, [Søren Bramer Schmidt](https://twitter.com/sorenbs)'s north star was always to simplify database interactions. He envisioned databases becoming as straightforward as creating a page in Notion, rather than a complex, fragile structure requiring constant attention. This vision was the seed that eventually blossomed into Data DX. Søren's personal philosophy was evident in Prisma's operations from the very beginning. Even when Prisma didn't explicitly name this approach, Data DX principles were already being practiced. The company's goal has consistently been to streamline and simplify how developers interact with databases, making it an intuitive and efficient process. ## Data DX in practice - -Prisma's products are designed to alleviate the complexities typically associated with working with databases. Developers don't need to be experts in database scaling, indexes, or cluster management. Prisma's intuitive tools make it seamless to get started, increase the level of abstraction, and provide developer-friendly interfaces, making building with data more accessible and less daunting. +Prisma's products are designed to alleviate the complexities typically associated with working with databases. Developers don't need to be experts in database scaling, indexes, or cluster management. Prisma's intuitive tools make it seamless to get started, increase the level of abstraction, and provide developer-friendly interfaces, making building with data more accessible and less daunting. Dedication to flexible accessibility, a core tenet of Data DX, means that Prisma is not only an easy-to-use solution for teams on day one, but is committed to remaining a reliable partner as teams scale up into production and enterprise. ## The ripple effect in the industry - [Data DX](https://www.datadx.io/) struck a chord across the ecosystem, with numerous companies such as [Cloudflare](https://www.cloudflare.com/), [Turso](https://turso.tech/), [Xata](https://xata.io/), [tinybird](https://www.tinybird.co/), [Grafbase](https://grafbase.com/), [PlanetScale](https://planetscale.com/), [Snaplet](https://www.snaplet.dev/), and [Supabase](https://supabase.com/) recognizing its value and standing with the manifesto’s principles as partners. This collective acknowledgment led to the establishment of Data DX as a distinct category as seen in the inaugural [Data DX event](https://www.datadx.io/event). Industry leaders have contributed to defining and enriching the concept of Data DX, highlighting its crucial role in supporting developers as they build effective, data-driven applications. ## Looking ahead: The future of Data DX - -Prisma's commitment to Data DX continues to be a driving force in its ongoing innovation and expansion. In collaboration with partners, Data DX will continue to evolve, ensuring developer experience is at the forefront of product creation. The early adoption of Data DX signals a promising future where developers can engage with data in more meaningful, efficient, and creative ways. +Prisma's commitment to Data DX continues to be a driving force in its ongoing innovation and expansion. In collaboration with partners, Data DX will continue to evolve, ensuring developer experience is at the forefront of product creation. The early adoption of Data DX signals a promising future where developers can engage with data in more meaningful, efficient, and creative ways. Data DX, as Søren aptly put, was always a part of Prisma's fabric—it just didn't have a label. Now, as an integral concept, it stands to simplify the complex, and enhance the overall experience of developing data-rich applications. diff --git a/apps/blog/content/blog/datamodel-v11-lrzqy1f56c90/index.mdx b/apps/blog/content/blog/datamodel-v11-lrzqy1f56c90/index.mdx index b719c7bea2..2d65047687 100644 --- a/apps/blog/content/blog/datamodel-v11-lrzqy1f56c90/index.mdx +++ b/apps/blog/content/blog/datamodel-v11-lrzqy1f56c90/index.mdx @@ -51,13 +51,14 @@ We have also invested a lot into the introspection of existing databases, enabli With the old datamodel syntax, tables and columns are always named _exactly_ after the models and fields in your datamodel. Using the new `@db` directive, you can control what tables and columns should be called in the underlying database: + + ```graphql type User @db(name: "user") { id: ID! @id name: String! @db(name: "full_name") } ``` - ```ts CREATE TABLE "default$default"."user" ( "id" varchar(25) NOT NULL, @@ -66,6 +67,7 @@ CREATE TABLE "default$default"."user" ( ); ``` + In this case, the underlying table will be called `user` and the column `full_name`. ### Decide how a relation is represented in the database schema @@ -83,6 +85,8 @@ With the new datamodel, developers can take full control over expressing a relat Here is an example with two relations (one is _inline_, the other uses a _relation table_): + + ```graphql type User { id: ID! @id @@ -100,7 +104,6 @@ type Post { author: User! } ``` - ```ts CREATE TABLE "default$default"."User" ( "id" varchar(25) NOT NULL, @@ -124,6 +127,7 @@ CREATE TABLE "default$default"."_PostToUser" ( ); ``` + In the case of the inline relation, the placement of the `@relation(link: INLINE)` directive determines on which end of the relation the foreign key is being stored, in this example it's stored in the `User` table. ### Use any field as `id`, `createdAt` or `updatedAt` @@ -132,6 +136,8 @@ With the old datamodel, developers were required to use reserved fields if they With the new `@id`, `@createdAt` and `@updatedAt` directives, it is now possible to add this functionality to any field of a model: + + ```graphql type User { myID: ID! @id @@ -139,7 +145,6 @@ type User { myUpdatedAt: DateTime! @updatedAt } ``` - ```ts CREATE TABLE "test$devasdas"."User" ( "myID" varchar(25) NOT NULL, @@ -149,6 +154,7 @@ CREATE TABLE "test$devasdas"."User" ( ); ``` + ### More flexible IDs The current datamodel _always_ uses [CUIDs](https://github.com/ericelliott/cuid) to generate and store globally unique IDs for database records. The datamodel v1.1 now makes it possible to maintain custom IDs as well as to use other ID types (e.g. integers, sequences, or UUIDs). @@ -168,10 +174,13 @@ For more extensive tutorials and instructions for getting started with an existi To install the latest version of the Prisma CLI, run: + + ```shell npm install -g prisma ``` + > When running Prisma with Docker, you need to upgrade its Docker image to `1.31`. ### Option A: Upgrade from an older Prisma version @@ -198,33 +207,32 @@ type User { } type Profile { -id: ID! @unique -user: User! -bio: String! + id: ID! @unique + user: User! + bio: String! } type Post { -id: ID! @unique -createdAt: DateTime! -updatedAt: DateTime! -title: String! -published: Boolean! @default(value: "false") -author: User! -categories: [Category!]! + id: ID! @unique + createdAt: DateTime! + updatedAt: DateTime! + title: String! + published: Boolean! @default(value: "false") + author: User! + categories: [Category!]! } type Category { -id: ID! @unique -name: String! -posts: [Post!]! + id: ID! @unique + name: String! + posts: [Post!]! } enum Role { -USER -ADMIN + USER + ADMIN } - -```` +``` When using the old datamodel, the following tables are created by Prisma in the underlying database: - `User` @@ -264,24 +272,31 @@ services: user: prisma password: prisma port: '5432' -```` +``` + Now upgrade the running Prisma server: + + ```shell docker-compose up -d ``` + #### 3. Generate new datamodel via introspection If you're now running `prisma deploy`, your Prisma CLI will throw an error because you're trying to deploy a datamodel in the old syntax to an updated Prisma server. The easiest way to fix these errors is by generating a datamodel written in the new syntax via introspection. Run the following command inside the directory where your `prisma.yml` is located: + + ```shell prisma introspect ``` + This introspects your database and generates another datamodel with the new syntax, called `datamodel-TIMESTAMP.prisma` (e.g. `datamodel-1554394432089.prisma`). For the example from above, the following datamodel is generated: ```graphql @@ -327,17 +342,19 @@ enum Role { ADMIN } ``` - #### 4. Deploy new datamodel The final step is to delete the old `datamodel.prisma` file and rename your generated datamodel to `datamodel.prisma` (so that the `datamodel` property in your `prisma.yml` points to the generated file that's using the new syntax). Once that's done, you can run: + + ```shell prisma deploy ``` + #### 5. Optimize your database schema Because the introspection didn't change anything about your database layout, all relations are still represented as relation tables. If you want to learn how you can migrate the old 1:1 and 1:n relations to use _foreign keys_, check out the docs [here](https://v1.prisma.io/docs/1.31/releases-and-maintenance/features-in-preview/datamodel-v11-b6a7/#4.-optimizing-the-database-schema). @@ -350,10 +367,13 @@ After having learned how to upgrade existing Prisma projects, we'll now walk you Let's start by setting up a new Prisma project: + + ```shell prisma init hello-datamodel ``` + In the interactive wizard, select the following: 1. Select **Create new database** @@ -364,20 +384,24 @@ Before launching the Prisma server and the database via Docker, enable port mapp In the generated `docker-compose.yml`, uncomment the following lines in the Docker image configuration of the database: + + ```yml ports: - - "5432:5432" + - '5432:5432' ``` - ```yml ports: - - "3306:3306" + - '3306:3306' ``` + #### 2. Define datamodel Let's define a datamodel that takes advantage of the new Prisma features. Open `datamodel.prisma` and replace the contents with the following: + + ```graphql type User @db(name: "user") { id: ID! @id @@ -421,6 +445,7 @@ enum Role { } ``` + Here are some important bits about this datamodel definition: - Each model is mapped a table that's named after the model but lowercased using the `@db` directive. @@ -439,10 +464,13 @@ Here are some important bits about this datamodel definition: In the next step, Prisma will map this datamodel to the underlying database: + + ```shell prisma deploy ``` + ##### `Category` @@ -456,7 +484,6 @@ CREATE TABLE "hello-datamodel$dev"."category" ( PRIMARY KEY ("id") ); ``` - Index: | index_name | index_algorithm | is_unique | column_name | @@ -478,7 +505,6 @@ CREATE TABLE "hello-datamodel$dev"."post" ( PRIMARY KEY ("id") ); ``` - Index: | index_name | index_algorithm | is_unique | column_name | @@ -495,7 +521,6 @@ CREATE TABLE "hello-datamodel$dev"."post_to_category" ( "post" varchar(25) NOT NULL ); ``` - Index: | index_name | index_algorithm | is_unique | column_name | @@ -514,7 +539,6 @@ CREATE TABLE "hello-datamodel$dev"."profile" ( PRIMARY KEY ("id") ); ``` - Index: | index_name | index_algorithm | is_unique | column_name | @@ -536,14 +560,12 @@ CREATE TABLE "hello-datamodel$dev"."user" ( PRIMARY KEY ("id") ); ``` - Index: | index_name | index_algorithm | is_unique | column_name | | ---------------------------------------- | --------------- | --------- | ----------- | | `user_pkey` | `BTREE` | `TRUE` | `id` | | `hello-datamodel$dev.user.email._UNIQUE` | `BTREE` | `TRUE` | `email` | - @@ -566,3 +588,4 @@ While the new datamodel syntax already incorporates many features requested by o We are currently working on a new [data modeling language](https://github.com/prisma/specs/tree/master/schema) that will be a variation of the currently used [SDL](https://www.prisma.io/blog/graphql-sdl-schema-definition-language-6755bcb9ce51). We'd love hear what you think of the new datamodel. Please share your feedback by [opening an issue in the feedback repo](https://github.com/prisma/datamodel-v1.1-feedback/issues/new) or join the conversation on [Spectrum](https://spectrum.chat/prisma/general/releasing-prisma-v1-31~0d4dcb59-a58f-4ecf-84e6-b8509bad4abf). + diff --git a/apps/blog/content/blog/documenting-apis-mjjpZ7E7NkVP/index.mdx b/apps/blog/content/blog/documenting-apis-mjjpZ7E7NkVP/index.mdx index d0b277d719..3eaf6823d0 100644 --- a/apps/blog/content/blog/documenting-apis-mjjpZ7E7NkVP/index.mdx +++ b/apps/blog/content/blog/documenting-apis-mjjpZ7E7NkVP/index.mdx @@ -17,10 +17,10 @@ Learn the importance of documenting web APIs, the different approaches available Application Programming Interfaces (APIs) are intermediaries that allow applications to communicate with each other. An example of this would be a frontend app retrieving data via the [GitHub API](https://docs.github.com/en/rest) to find out the number of stars a project has. -Web APIs (REST, GraphQL, gRPC) can send and receive data from other web APIs and frontend applications — from the web browser and mobile apps — over the [HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP) protocol using the client-server model. +Web APIs (REST, GraphQL, gRPC) can send and receive data from other web APIs and frontend applications — from the web browser and mobile apps — over the [HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP) protocol using the client-server model. This means a frontend application or web API (the client) sends a request to another web API (the server). The server replies with a response — data or an error — back to the client. -Before the client sends a request, it requires a number of details — the protocol, the URL/IP address where it's sending the request to, the structure of the request, route params and request queries, HTTP method and headers. +Before the client sends a request, it requires a number of details — the protocol, the URL/IP address where it's sending the request to, the structure of the request, route params and request queries, HTTP method and headers. The web API documentation provides these details for a developer to effectively use the API in their application. This article will discuss why you should document web APIs, the benefits, tools available, and bonus tools to improve web API development. @@ -30,7 +30,7 @@ This article will discuss why you should document web APIs, the benefits, tools API documentation is a _manual_ describing instructions on how to use an API — REST, gRPC or GraphQL. The documentation outlines the data structures, functions, arguments, return types, classes, etc., developers can refer to. -Building an API is half the job. An API lacking documentation would lead to friction between different teams building products. +Building an API is half the job. An API lacking documentation would lead to friction between different teams building products. Manual updates to documentation are a common problem that exists because they are prone to error. Failing to keep documentation up-to-date creates a point of failure for others who depend on it. - 13 min read - + 13 min read + -End-to-end type safety is implemented by ensuring the types across your entire application's stack are kept in sync. + End-to-end type safety is implemented by ensuring the types across your entire application's stack are kept in sync. ## Table Of Contents @@ -67,7 +67,7 @@ These are the main tools you will be using throughout this series: ### Assumed knowledge -While this series will attempt to cover everything in detail from a beginner's standpoint, the following would be helpful: +While this series will attempt to cover everything in detail from a beginner's standpoint, the following would be helpful: - Basic knowledge of JavaScript or TypeScript - Basic knowledge of GraphQL @@ -82,17 +82,16 @@ To follow along with the examples provided, you will be expected to have: ## Start a React application with Vite -There are many different ways to get started when building a React application. One of the easiest and most popular ways currently is to use [Vite](https://vitejs.dev/) to scaffold and set up your application. +There are many different ways to get started when building a React application. One of the easiest and most popular ways currently is to use [Vite](https://vitejs.dev/) to scaffold and set up your application. To get started, run this command in a directory where you would like your application's code to live: ```sh npm create vite@latest react-client -- --template react-ts ``` - > **Note**: You don't need to install any packages before running this command. -This command set up a ready-to-go React project in a folder named `react-client` using a TypeScript template. The template comes with a development server, hot module replacement, and a build process out of the box. +This command set up a ready-to-go React project in a folder named `react-client` using a TypeScript template. The template comes with a development server, hot module replacement, and a build process out of the box. Once your project has been generated you will be prompted to enter the new directory, install the node modules, and run the project. Go ahead and do that by running the following commands: @@ -101,7 +100,6 @@ cd react-client npm install npm run dev ``` - Once your development server is up and running you should see some output that looks similar to this: ![](/e2e-type-safety-graphql-react-1-I2GxIfxkSZ/imgs/localhost.png) @@ -125,12 +123,13 @@ Next, replace the contents of `/src/App.tsx` with the following component to giv // src/App.tsx function App() { - return

Hello World!

; + return ( +

Hello World!

+ ) } -export default App; +export default App ``` - ## Set up TailwindCSS Your application will use [TailwindCSS](https://tailwindcss.com/) to make designing and styling your components easy. To get started, you will first need a few new dependencies: @@ -138,35 +137,34 @@ Your application will use [TailwindCSS](https://tailwindcss.com/) to make design ```sh npm install -D tailwindcss postcss autoprefixer ``` - The command above will install all of the pieces TailwindCSS requires to work in your project, including the Tailwind CLI. Initialize TailwindCSS in your project using the newly installed CLI: ```sh npx tailwindcss init -p ``` - This command created two files in your project: - `tailwind.config.cjs`: The configuration file for TailwindCSS -- `postcss.config.cjs`: The configuration file for PostCSS +- `postcss.config.cjs`: The configuration file for PostCSS Within `tailwind.config.cjs`, you will see a `content` key. This is where you will define which files in your project TailwindCSS should be aware of when scanning through your code and deciding which of its classes and utilities you are using. This is how TailwindCSS determines what needs to be bundled into its built and minified output. Add the following value to the `content` key's array to tell TailwindCSS to look at any `.tsx` file within the `src` folder: ```js -diff; +diff // tailwind.config.cjs module.exports = { - content: [+"./src/**/*.tsx"], + content: [ ++ "./src/**/*.tsx" + ], theme: { extend: {}, }, plugins: [], }; ``` - Finally, within `src/index.css` you will need to import the TailwindCSS utilities, which are required to use TailwindCSS in your project. Replace that entire file's contents with the following: ```css @@ -176,7 +174,6 @@ Finally, within `src/index.css` you will need to import the TailwindCSS utilitie @tailwind components; @tailwind utilities; ``` - TailwindCSS is now configured and ready to go! Replace the existing `

` tag in `src/App.tsx` with this JSX to test that the TailwindCSS classes are working: ```tsx @@ -188,7 +185,6 @@ TailwindCSS is now configured and ready to go! Replace the existing `

` tag i

// ... ``` - If your webpage looks like this, congrats! You've successfully set up TailwindCSS! ![](/e2e-type-safety-graphql-react-1-I2GxIfxkSZ/imgs/tailwind-complete.png) @@ -206,17 +202,15 @@ First, create a new file in the `src` directory named `types.ts`: ```sh touch src/types.ts ``` - This is the file where you will store all of the types this application needs. Within that file, add and export a new `type` named `Message` with a `string` field named `body`: ```ts // src/types.ts export type Message = { - body: string; -}; + body: string +} ``` - This type describes what will be available within a `Message` object. There is only one key, however in a real-world application this may contain dozens or more field definitions. Next, add and export another type named `User` with a `name` field of the `string` type and a `messages` field that holds an array of `Message` objects: @@ -227,11 +221,10 @@ Next, add and export another type named `User` with a `name` field of the `strin // ... export type User = { - name: string; - messages: Message[]; -}; + name: string + messages: Message[] +} ``` - > **Note**: In the next sections of this series, you will replace these manually written types with automatically generated ones that contain up-to-date representations of your API's exposed data model. Now that your data has been "described", head over to `src/App.tsx`. Here you will mock some data to play with in your application. @@ -241,11 +234,10 @@ First, import the new `User` type into `src/App.tsx`: ```tsx // src/App.tsx -import { User } from "./types"; +import { User } from './types' // ... ``` - Next, within the `App` function in that file, create a new variable named `users` that contains an array of `User` objects with a single user entry who has a couple of messages associated with it: ```tsx @@ -254,26 +246,20 @@ Next, within the `App` function in that file, create a new variable named `users // ... function App() { - const users: User[] = [ - { - name: "Prisma Fan", - messages: [ - { - body: "Prisma rocks!!", - }, - { - body: "Did I mention I love Prisma?", - }, - ], - }, - ]; + const users: User[] = [{ + name: 'Prisma Fan', + messages: [{ + body: 'Prisma rocks!!' + }, { + body: 'Did I mention I love Prisma?' + }] + }] // ... } -export default Apps; +export default Apps ``` - In the snippet above, you defined a single user who has two associated messages. This is all the data you will need to build the UI components for this application. ## Display a list of users @@ -283,57 +269,52 @@ The first piece of the UI you will build is the component that displays a user. ```sh mkdir src/components ``` - Inside of that folder, create a file named `UserDisplay.tsx`: ```sh touch src/components/UserDisplay.tsx ``` - This file wil contain the user display component. To start that component off create a function named `UserDisplay` that returns a simple `

` tag for now. Then export that function: ```tsx // src/components/UserDisplay.tsx function UserDisplay() { - return

User Component

; + return

User Component

} -export default UserDisplay; +export default UserDisplay ``` - -This will serve as the skeleton for your component. The goal here is to allow this component to take in a `user` parameter and display that user's data inside of the component. +This will serve as the skeleton for your component. The goal here is to allow this component to take in a `user` parameter and display that user's data inside of the component. To accomplish this, first import your `User` type at the very top of `src/components/UserDisplay.tsx`: ```tsx // src/components/UserDisplay.tsx -import { User } from "../types"; +import { User } from '../types' // ... ``` - -You will use this type to describe what a `user` property in your `UserDisplay` function should contain. +You will use this type to describe what a `user` property in your `UserDisplay` function should contain. Add a new `type` to this file named `Props` with a single `user` field of the `User` type. Use that type to describe your function's arguments _(or "props")_: ```tsx // src/components/UserDisplay.tsx -import { User } from "../types"; +import { User } from '../types' type Props = { - user: User; -}; + user: User +} function UserDisplay({ user }: Props) { - return

User Component

; + return

User Component

} -export default UserDisplay; +export default UserDisplay ``` - > **Note**: The `user` key is being [destructured](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) within the function arguments to allow easy access to its values. The `user` property allows you to provide your component an object of type `User`. Each user in this application will be displayed within a rectangle that contains the user's name. @@ -345,44 +326,40 @@ Replace the existing `

` tag with the following JSX to display a user's name w // ... function UserDisplay({ user }: Props) { - return ( -

-
-

{user.name}

-
+ return
+
+

+ {user.name} +

+
- ); } // ... ``` - -This component is now ready to display a user's details, however you are not yet rendering it anywhere. +This component is now ready to display a user's details, however you are not yet rendering it anywhere. Head over to `src/App.tsx` and import your new component. Then, in place of the current `

` tag, render the component for each user in your `users` array: ```tsx // src/App.tsx -import { User } from "./types"; -import UserDisplay from "./components/UserDisplay"; +import { User } from './types' +import UserDisplay from './components/UserDisplay' function App() { - const users: User[] = [ - /**/ - ]; + const users: User[] = [/**/] return (
- {users.map((user, i) => ( - - ))} + { + users.map((user, i) => ) + }
- ); + ) } -export default App; +export default App ``` - If you head back to your browser you should see a nice box displaying your user's name! The only thing missing at this point is the user's messages. ![](/e2e-type-safety-graphql-react-1-I2GxIfxkSZ/imgs/user-displayed.png) @@ -396,7 +373,6 @@ Start off by creating a component to display an individual message. Create a new ```sh touch src/components/MessageDisplay.tsx ``` - Then, import the `Message` type from `src/types.ts` into the new file and create a `Props` type with two keys: - `message`: A `Message` object that holds the message details @@ -407,14 +383,13 @@ The result should look like the snippet below: ```tsx // src/components/MessageDisplay.tsx -import { Message } from "../types"; +import { Message } from '../types' type Props = { - message: Message; - index: number; -}; + message: Message + index: number +} ``` - With those pieces in place, you are ready to build the component function. The code below uses the `Props` type you wrote to describe the function arguments, pulls out the `message` and `index` values using destructuring, renders the message in a styled container, and finally exports the component: ```tsx @@ -423,20 +398,19 @@ With those pieces in place, you are ready to build the component function. The c // ... function MessageDisplay({ message, index }: Props) { - return ( -
-

{message.body}

+ return
+

+ {message.body} +

- ); } -export default MessageDisplay; +export default MessageDisplay ``` - Now it's time to put that component to use! In `src/components/UserDisplay.tsx` import the `MessageDisplay` component and render one for each element in the `user.messages` array: ```tsx -diff +diff // src/components/UserDisplay.tsx +import MessageDisplay from './MessageDisplay' @@ -457,20 +431,18 @@ function UserDisplay({ user }: Props) { } // ... ``` - Over in your browser, you should now see each user's messages to their right! ![](/e2e-type-safety-graphql-react-1-I2GxIfxkSZ/imgs/messages-displayed.png) -That looks great, however there is one last thing to add. You are building a tree view, so the final piece is to render "branches" that connect each message to its user. +That looks great, however there is one last thing to add. You are building a tree view, so the final piece is to render "branches" that connect each message to its user. Create a new file in `src/components` named `Branch.tsx`: ```sh touch src/components/Branch.tsx ``` - -This component will take in one property, `trunk`, which indicates whether or not the message it links to is the first in the list. +This component will take in one property, `trunk`, which indicates whether or not the message it links to is the first in the list. > **Note**: This is why you needed the `index` key in the `MessageDisplay` component. @@ -480,12 +452,11 @@ Insert the following component into that file: // src/components/Branch.tsx function Branch({ trunk }: { trunk: boolean }) { - return ( -
- ); } -export default Branch; +export default Branch ``` - The snippet above renders a branch with some crafty TailwindCSS magic. If you are interested in what TailwindCSS has to offer or want to better understand what is going on above, TailwindCSS has amazing [docs](https://tailwindcss.com/docs/installation) that cover all of the classes used above. To finish off this application's UI, use the new `Branch` component within your `MessageDisplay` component to render a branch for each message: ```tsx -diff +diff // src/components/MessageDisplay.tsx import { Message } from '../types' @@ -526,7 +495,6 @@ function MessageDisplay({ message, index}: Props) { export default MessageDisplay ``` - Back over in your browser, you will now see branches for each message! Hover over a message to highlight the branch ✨ ![](/e2e-type-safety-graphql-react-1-I2GxIfxkSZ/imgs/finished-ui.png) diff --git a/apps/blog/content/blog/e2e-type-safety-graphql-react-2-j9mEyHY0Ej/index.mdx b/apps/blog/content/blog/e2e-type-safety-graphql-react-2-j9mEyHY0Ej/index.mdx index ba2bebc484..54ba552972 100644 --- a/apps/blog/content/blog/e2e-type-safety-graphql-react-2-j9mEyHY0Ej/index.mdx +++ b/apps/blog/content/blog/e2e-type-safety-graphql-react-2-j9mEyHY0Ej/index.mdx @@ -17,10 +17,10 @@ tags: ---
- 10 min read -
+ 10 min read + -In this series you are learning how to implement end-to-end type safety using React, GraphQL, Prisma, and some other helpful tools that tie those three together. + In this series you are learning how to implement end-to-end type safety using React, GraphQL, Prisma, and some other helpful tools that tie those three together. ## Table Of Contents @@ -43,9 +43,10 @@ In this series you are learning how to implement end-to-end type safety using Re + ## Introduction -In this section, you will set up all of the pieces needed to build a GraphQL API. You will start up a TypeScript project, provision a PostgreSQL database, initialize Prisma in your project, and finally seed your database. +In this section, you will set up all of the pieces needed to build a GraphQL API. You will start up a TypeScript project, provision a PostgreSQL database, initialize Prisma in your project, and finally seed your database. In the process, you will set up an important piece of the end-to-end type-safety puzzle: a source of truth for the shape of your data. @@ -71,7 +72,7 @@ These are the main tools you will be using throughout this series: ### Assumed knowledge -While this series will attempt to cover everything in detail from a beginner's standpoint, the following would be helpful: +While this series will attempt to cover everything in detail from a beginner's standpoint, the following would be helpful: - Basic knowledge of JavaScript or TypeScript - Basic knowledge of GraphQL @@ -94,14 +95,12 @@ To kick things off, create a new folder in your working directory that will cont ```shell mkdir graphql-server # Example folder ``` - This project will use [npm](https://www.npmjs.com/), a package manager for Node.js, to manage and install new packages. Navigate into your new folder and initialize npm using the following commands: ```shell cd graphql-server npm init -y ``` - ### Install the basic packages While building this API, you will install various packages that will help in the development of your application. For now, install the following development packages: @@ -113,7 +112,6 @@ While building this API, you will install various packages that will help in the ```shell npm i -D ts-node-dev typescript @types/node ``` - > **Note**: These dependencies were installed as development dependencies because they are only needed during development. None of them are part of the production deployment. ### Set up TypeScript @@ -123,7 +121,6 @@ With TypeScript installed in your project, you can now initialize the TypeScript ```shell npx tsc --init ``` - The above command will create a new file named `tsconfig.json` at the root of your project and comes with a default set of configurations for how to compile and handle your TypeScript code. For the purposes of this series, you will leave the default settings. @@ -133,19 +130,17 @@ Create a new folder named `src` and within that folder a new file named `index.t mkdir src touch src/index.ts ``` - This will be the entry point to your TypeScript code. Within that file, add a simple `console.log`: ```typescript // src/index.ts -console.log("Hey there! 👋"); +console.log('Hey there! 👋'); ``` - ### Add a development script In order to run your code, you will use `ts-node-dev`, which will compile and run your TypeScript code and watch for file changes. -When a file is changed in your application, it will re-compile and re-run your code. +When a file is changed in your application, it will re-compile and re-run your code. Within `package.json`, in the `"scripts"` section, add a new script named `"dev"` that uses `ts-node-dev` to run your entry file: @@ -159,18 +154,16 @@ Within `package.json`, in the `"scripts"` section, add a new script named `"dev" }, // ... ``` - You can now use the following command to run your code: ```shell npm run dev ``` - ![](/e2e-type-safety-graphql-react-2-j9mEyHY0Ej/imgs/run-dev.png) ## Set up the database -The next piece you will set up is the database. You will be using a PostgreSQL database for this application. There are many different ways to host and work with a +The next piece you will set up is the database. You will be using a PostgreSQL database for this application. There are many different ways to host and work with a PostgreSQL database, however, one of the simplest ways is to deploy your database using [Railway](https://railway.app/). Head over to [https://railway.app](https://railway.app) and, if you don't already have one, create an account. @@ -202,7 +195,6 @@ To set up Prisma, you first need to install Prisma CLI as a development dependen ```shell npm i -D prisma ``` - ### Initialize Prisma With Prisma CLI installed, you will have access to a set of useful tools and commands provided by Prisma. The command you will use here is called `init`, and will initialize Prisma in your project: @@ -210,7 +202,6 @@ With Prisma CLI installed, you will have access to a set of useful tools and com ```shell npx prisma init ``` - This command will create a new `prisma` folder within your project. Inside this folder you will find a file, `schema.prisma`, which contains the start of a Prisma schema. That file uses the Prisma Schema Language _(PSL)_ and is where you will define your database's tables and fields. It currently looks as follows: @@ -227,12 +218,10 @@ datasource db { url = env("DATABASE_URL") } ``` - -Within the `datasource` block, note the `url` field. This fields equals a value `env("DATABASE_URL")`. This value tells Prisma to look within the environment variables for a +Within the `datasource` block, note the `url` field. This fields equals a value `env("DATABASE_URL")`. This value tells Prisma to look within the environment variables for a variable named `DATABASE_URL` to find the database's connection string. ### Set the environment variable - `prisma init` also created a `.env` file for you with a single variable named `DATABASE_URL`. This variable holds the connection string Prisma will use to connect to your database. Replace the current default contents of that variable with the connection string you retrieved via the Railway UI: @@ -241,9 +230,8 @@ Replace the current default contents of that variable with the connection string # .env # Example: postgresql://postgres:Pb98NuLZM22ptNuR4Erq@containers-us-west-63.railway.app:6049/railway -DATABASE_URL="" +DATABASE_URL="" ``` - ### Model your data The application you are building will need two different database tables: `User` and `Message`. Each "user" will be able to have many associated "messages". @@ -267,7 +255,6 @@ model User { createdAt DateTime @default(now()) } ``` - Next, add a `Message` model with the following fields: - `id`: The unique ID of the database record @@ -283,11 +270,10 @@ model Message { createdAt DateTime @default(now()) } ``` - Finally, set up a one-to-many relation between the `User` and `Message` tables. ```prisma -diff +diff // prisma/schema.prisma model User { @@ -305,7 +291,6 @@ model Message { + user User @relation(fields: [userId], references: [id]) } ``` - This data modeling step is an important one. What you have done here is set up the _source of truth_ for the shape of your data. You database's schema is now defined in one central place, and used to generate a type-safe API that interacts with that database. @@ -320,9 +305,9 @@ Run the following command to create and apply a migration to your database: ```shell npx prisma migrate dev --name init ``` - The above command will create a new migration file named `init`, apply that migration to your database, and finally generate Prisma Client based off of that schema. + If you head back over to the Railway UI, in the **Data** tab you should see your tables listed. If so, the migration worked and your database is ready to be put to work! ![](/e2e-type-safety-graphql-react-2-j9mEyHY0Ej/imgs/railway-tables.png) @@ -336,7 +321,6 @@ Within the `prisma` folder, create a new file named `seed.ts`: ```shell touch prisma/seed.ts ``` - Paste the following contents into that file: ```typescript @@ -401,7 +385,6 @@ main().then(() => { console.log("Data seeded..."); }); ``` - This script clears out the database and then creates three users. Each user is given two messages associated with it. > **Note**: In the next article, you will dive deeper into the process writing a few queries using Prisma Client. @@ -417,13 +400,11 @@ Now that the seed script is available, head over to your `package.json` file and }, // ... ``` - Use the following command to run your seed script: ```shell npx prisma db seed ``` - After running the script, if you head back to the Railway UI and into the **Data** tab, you should be able to navigate through the newly added data. ![](/e2e-type-safety-graphql-react-2-j9mEyHY0Ej/imgs/railway-data.png) diff --git a/apps/blog/content/blog/elsevier-customer-story-SsAASKagMHtN/index.mdx b/apps/blog/content/blog/elsevier-customer-story-SsAASKagMHtN/index.mdx index a6627d8307..7adf6122c5 100644 --- a/apps/blog/content/blog/elsevier-customer-story-SsAASKagMHtN/index.mdx +++ b/apps/blog/content/blog/elsevier-customer-story-SsAASKagMHtN/index.mdx @@ -19,15 +19,15 @@ With the help of Prisma, Elsevier is in the process of modernizing the scientifi ## Contributing to advances in science and healthcare -[Elsevier's](https://www.elsevier.com/) mission of helping researchers and healthcare professionals is rooted in publishing and has also evolved into a global leader in information and analytics. With so much health-related information being shared in real time, Elsevier decided it was time to modernize and speed up their existing manual peer review process. +[Elsevier's](https://www.elsevier.com/) mission of helping researchers and healthcare professionals is rooted in publishing and has also evolved into a global leader in information and analytics. With so much health-related information being shared in real time, Elsevier decided it was time to modernize and speed up their existing manual peer review process. ![Peer Review Workflow](/elsevier-customer-story-SsAASKagMHtN/imgs/peer-review-diagram-V3.png) -Building an application to speed up the peer review process would help Elsevier remain a leader in healthcare research. They dedicated a small project team consisting of Serghei Ghidora (Tech Lead), Paul Foeckler (Product Owner), and a UX Designer to develop a minimum viable product (MVP) to make the peer review process faster and more efficient. +Building an application to speed up the peer review process would help Elsevier remain a leader in healthcare research. They dedicated a small project team consisting of Serghei Ghidora (Tech Lead), Paul Foeckler (Product Owner), and a UX Designer to develop a minimum viable product (MVP) to make the peer review process faster and more efficient. ## Setting a strong foundation with Prisma -Streamlining a very manual, logically complex publication process is a tall task. Serghei knew that being flexible was going to be key to developing a successful MVP. +Streamlining a very manual, logically complex publication process is a tall task. Serghei knew that being flexible was going to be key to developing a successful MVP. -"When it comes to the data model experimentation, handling migrations and things like that is just amazing. That you are able to add something or remove something in Prisma, and you run the migrations and Prisma will do everything by itself." +"When it comes to the data model experimentation, handling migrations and things like that is just amazing. That you are able to add something or remove something in Prisma, and you run the migrations and Prisma will do everything by itself." @@ -107,6 +109,8 @@ Serghei understood the importance of selecting technologies that were going to a The flexibility proved to be a major contributor to a single tech lead producing a meaningful product in just ten months. + + ## The application's architecture In addition to Prisma, Serghei utilized several other technologies to achieve their MVP. The project structure looks like the following, with Prisma serving types to multiple apps. diff --git a/apps/blog/content/blog/enabling-cors-for-express-graphql-apollo-server-1ef999bfb38d/index.mdx b/apps/blog/content/blog/enabling-cors-for-express-graphql-apollo-server-1ef999bfb38d/index.mdx index a80d1e05a4..c63e4480c4 100644 --- a/apps/blog/content/blog/enabling-cors-for-express-graphql-apollo-server-1ef999bfb38d/index.mdx +++ b/apps/blog/content/blog/enabling-cors-for-express-graphql-apollo-server-1ef999bfb38d/index.mdx @@ -33,78 +33,76 @@ In fact, we prepared an [example](https://github.com/nikolasburk/graphql-cors-ex ### Server ```js -const express = require("express"); -const bodyParser = require("body-parser"); -const cors = require("cors"); -const { graphqlExpress, graphiqlExpress } = require("apollo-server-express"); -const { makeExecutableSchema } = require("graphql-tools"); +const express = require('express') +const bodyParser = require('body-parser') +const cors = require('cors') +const { graphqlExpress, graphiqlExpress } = require('apollo-server-express') +const { makeExecutableSchema } = require('graphql-tools') const typeDefs = ` type Query { hello(name: String): String! } -`; +` const resolvers = { Query: { - hello: (_, { name }) => `Hello ${name || "World"}`, + hello: (_, { name }) => `Hello ${name || 'World'}`, }, -}; +} -const myGraphQLSchema = makeExecutableSchema({ typeDefs, resolvers }); -const PORT = 4000; -const app = express(); +const myGraphQLSchema = makeExecutableSchema({ typeDefs, resolvers }) +const PORT = 4000 +const app = express() // app.use(cors()) // not having cors enabled will cause an access control error -app.use("/graphql", bodyParser.json(), graphqlExpress({ schema: myGraphQLSchema })); -app.get("/graphiql", graphiqlExpress({ endpointURL: "/graphql" })); +app.use('/graphql', bodyParser.json(), graphqlExpress({ schema: myGraphQLSchema })) +app.get('/graphiql', graphiqlExpress({ endpointURL: '/graphql' })) -console.log(`Server listening on http://localhost:${PORT} ...`); -app.listen(PORT); +console.log(`Server listening on http://localhost:${PORT} ...`) +app.listen(PORT) ``` - > Notice that line 23 is commented out — CORS is not enabled! ### Frontend ```js -import React from "react"; -import ReactDOM from "react-dom"; -import "./index.css"; -import App from "./App"; -import registerServiceWorker from "./registerServiceWorker"; -import { ApolloProvider } from "react-apollo"; -import { ApolloClient, HttpLink, InMemoryCache } from "apollo-client-preset"; +import React from 'react' +import ReactDOM from 'react-dom' +import './index.css' +import App from './App' +import registerServiceWorker from './registerServiceWorker' +import { ApolloProvider } from 'react-apollo' +import { ApolloClient, HttpLink, InMemoryCache } from 'apollo-client-preset' -const httpLink = new HttpLink({ uri: "http://localhost:4000/graphql" }); +const httpLink = new HttpLink({ uri: 'http://localhost:4000/graphql' }) const client = new ApolloClient({ link: httpLink, cache: new InMemoryCache(), -}); +}) ReactDOM.render( , - document.getElementById("root"), -); -registerServiceWorker(); + document.getElementById('root'), +) +registerServiceWorker() ``` - > Standard setup for using Apollo Client 2.0 ```js -import React, { Component } from "react"; -import logo from "./logo.svg"; -import "./App.css"; -import gql from "graphql-tag"; -import { graphql } from "react-apollo"; +import React, { Component } from 'react' +import logo from './logo.svg' +import './App.css' +import gql from 'graphql-tag' +import { graphql } from 'react-apollo' class App extends Component { render() { if (this.props.data.loading) { - return
Loading
; + return
Loading
} return (
@@ -112,17 +110,18 @@ class App extends Component {

{this.props.data.hello}

- ); + ) } } -export default graphql(gql` - { - hello - } -`)(App); +export default graphql( + gql` + { + hello + } + `, +)(App) ``` - > The `App` component sends a simple `hello` query using Apollo Client In your local development setup, where the React app is loaded from [`http://localhost:3000`](http://localhost:3000) and the GraphQL server is serving at [`http://localhost:4000`](http://localhost:4000)/graphql, you’ll now get an access control error if you’re trying to run the app: @@ -151,12 +150,12 @@ When using `express-graphql` and `apollo-server`, all you need to do is include ```js // other imports ... -const cors = require("cors"); +const cors = require('cors') -const app = express(); +const app = express() -app.use(cors()); // enable `cors` to set HTTP response header: Access-Control-Allow-Origin: * -app.use("/graphql", bodyParser.json(), graphqlExpress({ schema })); +app.use(cors()) // enable `cors` to set HTTP response header: Access-Control-Allow-Origin: * +app.use('/graphql', bodyParser.json(), graphqlExpress({ schema })) -app.listen(PORT); -``` +app.listen(PORT) +``` \ No newline at end of file diff --git a/apps/blog/content/blog/esop-exercise-windows/index.mdx b/apps/blog/content/blog/esop-exercise-windows/index.mdx index d02908595f..a43eec6958 100644 --- a/apps/blog/content/blog/esop-exercise-windows/index.mdx +++ b/apps/blog/content/blog/esop-exercise-windows/index.mdx @@ -11,11 +11,11 @@ heroImagePath: "/esop-exercise-windows/imgs/hero-187cb6c0e9ac7a4c48892a3e196329e heroImageAlt: "Extended ESOP Exercise Windows" --- -As a progressive OSS SaaS company, we are expanding our commitment to transparency by sharing insights into our operational practices, starting with our approach to stock options. +As a progressive OSS SaaS company, we are expanding our commitment to transparency by sharing insights into our operational practices, starting with our approach to stock options. This move reflects our belief that sharing knowledge about equitable employee compensation is as vital as sharing code, offering a blueprint for building sustainable, employee-centric businesses. Stay tuned as we embark on this expanded journey of openness, hoping to inspire and inform alike. -Employee Stock Options Programs (ESOPs) are commonplace in the startup ecosystem, with nearly every startup in the USA and Europe, including Prisma, implementing some form of ESOP. They evoke a wide range of opinions and emotions, and their consequences have become a source of folklore and myths. Some perceive ESOPs as a means to extraordinary wealth, while others regard them as mere lottery tickets or tools to persuade employees to accept lower salaries. These differing views are a reflection of the reality shaped by the design of ESOPs. +Employee Stock Options Programs (ESOPs) are commonplace in the startup ecosystem, with nearly every startup in the USA and Europe, including Prisma, implementing some form of ESOP. They evoke a wide range of opinions and emotions, and their consequences have become a source of folklore and myths. Some perceive ESOPs as a means to extraordinary wealth, while others regard them as mere lottery tickets or tools to persuade employees to accept lower salaries. These differing views are a reflection of the reality shaped by the design of ESOPs. This blog post aims to discuss the concept of 90-day exercise windows in programs and explain why we decided to extend this period to 10 years at Prisma. We believe that this change is not only fair to our team, but also a good business decision. @@ -41,25 +41,26 @@ Our 10-year exercise window reinforces this belief by providing past team member - time for more information to be gained about Prisma’s prospective success and thereby - reducing the overall risk of the investment. -Finally, this change benefited many of our team members based in Berlin, Germany, where we previously had our largest office. Unfavorable German tax laws affected a significant number of our team, and this adjustment helped them avoid extraordinarily high tax payments if they were to exercise their options upon departure. +Finally, this change benefited many of our team members based in Berlin, Germany, where we previously had our largest office. Unfavorable German tax laws affected a significant number of our team, and this adjustment helped them avoid extraordinarily high tax payments if they were to exercise their options upon departure. -Although extended exercise periods are popular with employees, there are ample critics who have valid concerns about this approach, as illustrated by [this post](https://a16z.com/the-lack-of-options-for-startup-employees-options/) from Andreessen Horowitz. +Although extended exercise periods are popular with employees, there are ample critics who have valid concerns about this approach, as illustrated by [this post](https://a16z.com/the-lack-of-options-for-startup-employees-options/) from Andreessen Horowitz. ## Criticism of extended exercise windows -From a _fairness_ perspective, critics have pointed out that the longer exercise period will perpetuate a _wealth transfer_. Former team members, who are no longer contributing to the company can hold on to their options for an extended period, benefiting from the work of current team members. +From a *fairness* perspective, critics have pointed out that the longer exercise period will perpetuate a *wealth transfer*. Former team members, who are no longer contributing to the company can hold on to their options for an extended period, benefiting from the work of current team members. -From an _economics_ perspective, it is argued that the prolonged exercise window may lead to an increased rate of dilution for current employees. +From an *economics* perspective, it is argued that the prolonged exercise window may lead to an increased rate of dilution for current employees. -> _Dilution is the decrease in ownership percentage for existing shareholders when a company issues or reserves new shares of stock. —_ ([Carta](https://carta.com/blog/how-to-manage-equity-dilution-as-an-early-stage-startup/)) +> *Dilution is the decrease in ownership percentage for existing shareholders when a company issues or reserves new shares of stock. —* ([Carta](https://carta.com/blog/how-to-manage-equity-dilution-as-an-early-stage-startup/)) +> -It is argued that dilution would be amplified because of former employees retaining their options, forcing the company to refresh the option pool more frequently to attract new talent or incentivize existing team-members. This dilution can impact the ownership percentage of current team members in the company. +It is argued that dilution would be amplified because of former employees retaining their options, forcing the company to refresh the option pool more frequently to attract new talent or incentivize existing team-members. This dilution can impact the ownership percentage of current team members in the company. These concerns are not entirely wrong, and could be mitigated by a change in perspective, and some adjusted financial modelling. Below are a few perspectives we hod at Prisma seeks a compromise between the interests of current team-members, former team-members, founders, investors, and other shareholders. ### About fairness: Standing on the shoulders of giants -The concern raised by critics about _"wealth transfer"_ overlooks a fundamental issue. Many new and current team members can only join a company because of the success achieved by those who came before them. It also seems unfair that a team-member who joined when the startup was just starting out should have to take a much bigger financial risk to make money from their stock options compared to an employee who joined later. One way to address this is by offering larger stock option grants to early-stage employees, typically coupled with a lower exercise price. This is intended to serve as compensation for the risk involved. +The concern raised by critics about *"wealth transfer"* overlooks a fundamental issue. Many new and current team members can only join a company because of the success achieved by those who came before them. It also seems unfair that a team-member who joined when the startup was just starting out should have to take a much bigger financial risk to make money from their stock options compared to an employee who joined later. One way to address this is by offering larger stock option grants to early-stage employees, typically coupled with a lower exercise price. This is intended to serve as compensation for the risk involved. However, some people argue that employees in the early stages should accept lower salaries in exchange for these bigger stock option grants. This means that early-stage employees have to accept both lower salaries and more investment risk. It is not clear whether the larger stock option grants and exercise price they receive are enough to make up for these sacrifices. @@ -67,7 +68,7 @@ Resolving fairness problems is always difficult. Having longer exercise periods ### About economics: Mitigating dilution through increased present value -The second concern — the economics of extended exercise windows and its effect on dilution — is also not complete. By considering the _Present Value_ (PV) of stock options, a different approach can be pursued. +The second concern — the economics of extended exercise windows and its effect on dilution — is also not complete. By considering the *Present Value* (PV) of stock options, a different approach can be pursued. Specifically, extending the exercise window for stock options increases the chances for team members to benefit from their options. This is because the probability of a liquidity event (IPO, purchase, secondary sale, etc.) increases with time. Due to the time value of money, the PV of stock options with a longer (e.g. 10-year) exercise window would therefore be more than the PV of stock options with a shorter (e.g. 90-day) exercise window - all things being equal. @@ -75,7 +76,7 @@ Due to the higher PV, startups could justifiably reduce the size of stock option By reducing the size of stock option grants and increasing the exercise windows, more employees will be able to benefit from their stock options, although the size of the individual grants would be smaller. This more equitable distribution would eliminate the stark contrast between employees who can exercise their stock options and those who cannot. This would ultimately increase the perceived value of Employee Stock Ownership Plans (ESOPs) as more individuals benefit from them. Additionally, this could enhance the overall perceived value of compensation packages without incurring any cost to companies. -Following this, there is a strong business case to increase exercise windows to benefit from the economics. The question is really not _if_ this should be done, but rather _how_ this should be done. 10 Year exercise windows are not always the best option. It will depend on the _Startup_ to _IPO_ timeframe a company expects, among other factors. In addition, arriving at a Present Value would require the company to make some impossible assumptions about the _Future Value_ (FV), and the _Rate of Return_ (r). Ultimately, the idea is not to try and engage in detailed financial modeling but rather to implement an approach that approximates future expectations and changes over time as more information becomes available. +Following this, there is a strong business case to increase exercise windows to benefit from the economics. The question is really not *if* this should be done, but rather *how* this should be done. 10 Year exercise windows are not always the best option. It will depend on the *Startup* to *IPO* timeframe a company expects, among other factors. In addition, arriving at a Present Value would require the company to make some impossible assumptions about the *Future Value* (FV), and the *Rate of Return* (r). Ultimately, the idea is not to try and engage in detailed financial modeling but rather to implement an approach that approximates future expectations and changes over time as more information becomes available. ## Unintended consequences diff --git a/apps/blog/content/blog/formbricks-and-prisma-accelerate-solving-scalability-together/index.mdx b/apps/blog/content/blog/formbricks-and-prisma-accelerate-solving-scalability-together/index.mdx index 6ed4579a5c..e5ca1bad32 100644 --- a/apps/blog/content/blog/formbricks-and-prisma-accelerate-solving-scalability-together/index.mdx +++ b/apps/blog/content/blog/formbricks-and-prisma-accelerate-solving-scalability-together/index.mdx @@ -36,17 +36,13 @@ The implementation of Prisma Accelerate came at a crucial time. Formbricks had j The transition to Prisma Accelerate happened during a night of intense work, set against the backdrop of trying to fix a failing database at 3 a.m. in South Korea. Despite the challenging circumstances, setting up Accelerate was straightforward. - - It's also possible to set up Accelerate when you're totally tired at 3 a.m. in the morning. The - process was pretty straightforward – created an account, replaced the connection string, and - connected the database on the Prisma Accelerate website. It was easy and worked out, even under - those high-pressure circumstances. + It's also possible to set up Accelerate when you're totally tired at 3 a.m. in the morning. The process was pretty straightforward – created an account, replaced the connection string, and connected the database on the Prisma Accelerate website. It was easy and worked out, even under those high-pressure circumstances. ## The impact of Prisma Accelerate @@ -59,6 +55,6 @@ For Formbricks, a platform where real-time user feedback is crucial, the ability Formbricks' journey with Prisma Accelerate is a clear example of how the right technological partnership alongside solving immediate problems, can set a foundation for future growth and stability. As they continue to evolve and expand, the scalability and efficiency provided by Prisma Accelerate will remain a key component of their success. ---- +----- Interested in trying Prisma Accelerate for yourself? Head over to our [Platform Console](https://console.prisma.io/login?source=blog?medium=customer-story-formbricks) to get started! diff --git a/apps/blog/content/blog/from-rust-to-typescript-a-new-chapter-for-prisma-orm/index.mdx b/apps/blog/content/blog/from-rust-to-typescript-a-new-chapter-for-prisma-orm/index.mdx index 51a29a68ba..2d7b697ad8 100644 --- a/apps/blog/content/blog/from-rust-to-typescript-a-new-chapter-for-prisma-orm/index.mdx +++ b/apps/blog/content/blog/from-rust-to-typescript-a-new-chapter-for-prisma-orm/index.mdx @@ -34,7 +34,7 @@ In our [recently released ORM Manifesto](https://www.prisma.io/blog/prisma-orm-m This may have been only a sentence in our post, but it has caused quite a few reactions: - + For example we really loved this video from Theo: @@ -45,7 +45,7 @@ In short, we want to let everyone in the community know **what is changing, the ## Why did Prisma choose Rust? -Before we can explore the future of Prisma ORM, we need to understand why Prisma ORM uses a Rust engine. When we started planning Prisma 2 (now known as Prisma ORM), we had a pretty clear vision: we wanted to build ORMs for as many languages as possible—TypeScript, Go, Python, Scala, Rust, and others. We needed a solution that would make adding support for new languages relatively straightforward. Rust’s performance benefits and systems-level approach made it a natural choice for this core query engine. +Before we can explore the future of Prisma ORM, we need to understand why Prisma ORM uses a Rust engine. When we started planning Prisma 2 (now known as Prisma ORM), we had a pretty clear vision: we wanted to build ORMs for as many languages as possible—TypeScript, Go, Python, Scala, Rust, and others. We needed a solution that would make adding support for new languages relatively straightforward. Rust’s performance benefits and systems-level approach made it a natural choice for this core query engine. This decision was also a continuation of the work done on GraphCool and Prisma 1. The core, deployable infrastructure of these earlier solutions evolved into the Rust-based query engine—a binary designed to handle the heavy lifting of generating SQL queries, managing connection pools, and returning results from your database. This freed up language-specific clients like `prisma-client-js` to remain lightweight layers on top of the engine. @@ -71,8 +71,8 @@ To understand this change, let’s review the current query engine setup. Today, there are two ways that you can query a database with Prisma ORM: -- Using a database driver written in _Rust_. -- Using a [driver adapter](https://www.prisma.io/docs/orm/overview/databases/database-drivers#driver-adapters) and driver both written in _TypeScript_. +- Using a database driver written in *Rust*. +- Using a [driver adapter](https://www.prisma.io/docs/orm/overview/databases/database-drivers#driver-adapters) and driver both written in *TypeScript*. In the first approach, Prisma ORM queries are passed to the query engine, written in Rust. This engine manages everything from building the query plan to executing queries and returning results to the JavaScript client: @@ -113,7 +113,7 @@ For instance, we plan to remove the requirement for `binaryTargets`, further str ### Unlocking future opportunities -This transition isn’t just about addressing current challenges—it creates new opportunities for innovation. In fact, the query compiler enables many possibilities for our team and the community to explore. For example, the use of parameterized query plans could allow for **saving query plans for re-use** to speed up execution. Another avenue would be to build the initial query plans _at compile time_, further reducing runtime computation needs. +This transition isn’t just about addressing current challenges—it creates new opportunities for innovation. In fact, the query compiler enables many possibilities for our team and the community to explore. For example, the use of parameterized query plans could allow for **saving query plans for re-use** to speed up execution. Another avenue would be to build the initial query plans *at compile time*, further reducing runtime computation needs. We’re excited about these possibilities and eager to hear your thoughts! Join the discussion on our GitHub or Discord. @@ -133,7 +133,7 @@ Finally, test our Early Access client! We’ll share updates on GitHub and Disco This is an exciting time for Prisma, with even more improvements and opportunities ahead. Thank you for inspiring us to grow and for being part of this journey. -_Want to be among the first to try our new Early Access client? [Follow us on X](https://pris.ly/x) and [join our Discord](https://pris.ly/discord) to stay updated._ +*Want to be among the first to try our new Early Access client? [Follow us on X](https://pris.ly/x) and [join our Discord](https://pris.ly/discord) to stay updated.* ## Frequently Asked Questions (FAQ) diff --git a/apps/blog/content/blog/full-stack-typesafety-with-angular-nest-nx-and-prisma-CcMK7fbQfTWc/index.mdx b/apps/blog/content/blog/full-stack-typesafety-with-angular-nest-nx-and-prisma-CcMK7fbQfTWc/index.mdx index 8321d3a677..1789674e56 100644 --- a/apps/blog/content/blog/full-stack-typesafety-with-angular-nest-nx-and-prisma-CcMK7fbQfTWc/index.mdx +++ b/apps/blog/content/blog/full-stack-typesafety-with-angular-nest-nx-and-prisma-CcMK7fbQfTWc/index.mdx @@ -38,20 +38,26 @@ Let's start by creating an Nx workspace for our project. We'll use the `create-n In a terminal window, create a workspace with a preset of `angular`. + + ```shell npx create-nx-workspace --preset=angular ``` + An interactive prompt takes us through the setup process. Select a name for the workspace and application and then continue through the prompts. ![Interactive prompts for setting up an Nx workspace](/full-stack-typesafety-with-angular-nest-nx-and-prisma-CcMK7fbQfTWc/imgs/nx-prisma-1.png) Once Nx finishes wiring up the workspace, open it up and try running the Angular application. + + ```shell npm start ``` + This command will tell Nx to serve the Angular application that was created as the workspace initialized. After it compiles, open up `localhost:4200` to make sure everything is working. ![The Angular application running on localhost:4200](/full-stack-typesafety-with-angular-nest-nx-and-prisma-CcMK7fbQfTWc/imgs/nx-prisma-2.png) @@ -62,24 +68,33 @@ Our front end is ready to go but we haven't yet included a project for the backe To add our NestJS project, we first need to install the official NestJS plugin for Nx. In a new terminal window, grab the `@nrwl/nx` package from npm. + + ```shell npm install -D @nrwl/nest ``` + After installation, use the plugin to generate a NestJS project within the workspace. Since we'll only have one backend project for this example, let's just name it "api". + + ```shell nx generate @nrwl/nest:application api ``` + Once the generator finishes, we can see a new folder called `api` under the `apps` directory. This is where our NestJS app lives. The default NestJS installation comes with a single endpoint which returns a "hello world" message. Let's start the API and make sure we can access the endpoint. To start the API, target the `nx serve` command directly at the NestJS app. + + ```shell nx serve api ``` + Once the API is up and running, go to `http://localhost:3333/api` in the browser and make sure you can see the "hello world" message. ![The NestJS application running on localhost:3333./imgs/nx-prisma-3.png) @@ -90,19 +105,25 @@ Now that we've got our front end and backend projects in place, let's set up Pri We need to install two packages to work with Prisma: the Prisma Client (as a regular dependency) and the Prisma CLI (as a dev dependency). + + ```shell npm install @prisma/client npm install -D @prisma/cli ``` + The Prisma Client is what gives us ORM-style type-safe database access in our code. The Prisma CLI is what gives us a set of commands to initialize Prisma, create database migrations, and more. With those packages installed, let's initialize Prisma. + + ```shell npx prisma init ``` + After running this command, a `prisma` directory is created at the workspace root. Inside is a single file called `schema.prisma`. This file uses the [Prisma Schema Language](https://www.prisma.io/docs/concepts/components/prisma-schema) and is the place where we define the shape of our database. We use it to describe the tables for our databases and their columns, the relationships between tables, and more. @@ -111,6 +132,8 @@ When we create a Prisma model, we need to select a `provider` for our datasource Instead of using Postgres, let's use SQLite so we can keep things simple. Switch up the `db` datasource so that uses SQLite. Point the `url` parameter to a file called `dev.db` within the filesystem. + + ```prisma datasource db { provider = "sqlite" @@ -118,10 +141,13 @@ datasource db { } ``` + **Note:** We don't need to create the `dev.db` file ourselves. Its creation will be taken care of for us in a later step. Let's now set up a simple model for our shop. To get ourselves started, let's work with a single table called `Product`. To do so, create a new `model` in the schema file and give it some fields. + + ```prisma model Product { id String @id @default(cuid()) @@ -133,14 +159,18 @@ model Product { } ``` + The `id` field is marked as the primary key via the `@id` directive. We're also setting its default value to be a collision-resistant unique ID. The other fields and fairly straight-forward in their purpose. With the model in place, let's run our first migration so that the filesystem database file gets created and populated with our `Product` table. + + ```shell npx prisma migrate dev --preview-feature ``` + An interactive prompt will ask for the name of the migration. Call it whatever you like, something like `init` works fine. After the migration completes, a `dev.db` file is created in the `prisma` directory, along with a `migrations` directory. It's within the `migrations` directory that all of the SQL that's used to perform our database migrations is stored. Since these files are raw SQL, we have the opportunity to adjust them before they operate on our databases. Read the [migrate docs](https://www.prisma.io/docs/concepts/components/prisma-migrate) to find out more about how you can customize the migration behavior. @@ -151,10 +181,13 @@ With the database in place and populated with a table, we can now take a look at In a new terminal window, use the Prisma CLI to fire up Prisma Studio. + + ```shell npx prisma studio ``` + Running this command will open Prisma Studio. In the browser, it opens at `localhost:5555`. ![Prisma Studio running at localhost:5555](/full-stack-typesafety-with-angular-nest-nx-and-prisma-CcMK7fbQfTWc/imgs/nx-prisma-4.png) @@ -177,32 +210,38 @@ The data in our database is ready to go. What we need now is an endpoint we can Use the NestJS Nx plugin to generate a new library called `products`. Include a controller and a service within. + + ```shell nx generate @nrwl/nest:library products --controller --service ``` + We'll create a method in the service to reach into our database to get the data. Then, in the controller, we'll expose a `GET` endpoint which uses the service to get that data and return it to the client. Let's start by building out the database query within the service. This is the first spot we'll see Prisma's types really shine! Within `products.service.ts`, import `PrismaClient`, create an instance of it, and expose a `public` method to query for the data. + + ```ts // libs/products/src/lib/products.service.ts -import { Injectable } from "@nestjs/common"; -import { PrismaClient, Product } from "@prisma/client"; +import { Injectable } from '@nestjs/common' +import { PrismaClient, Product } from '@prisma/client' -const prisma = new PrismaClient(); +const prisma = new PrismaClient() @Injectable() export class ProductService { public getProducts(): Promise { - return prisma.product.findMany(); + return prisma.product.findMany() } } ``` + We're importing two things from `@prisma/client` here: `PrismaClient` and `Product`. `PrismaClient` is what we use to create an instance of our database client and it exposes methods and properties that are useful for querying the database. @@ -213,37 +252,42 @@ The `Product` import is the TypeScript type that was generated for us by Prisma Let's now work within the controller to make a call to `getProducts` to fetch the data. Open up `products.controller.ts` and add a method which responds to `GET` requests. + + ```ts // libs/products/src/lib/products.controller.ts -import { Controller, Get } from "@nestjs/common"; -import { ProductsService } from "./products.service"; +import { Controller, Get } from '@nestjs/common' +import { ProductsService } from './products.service' -@Controller("products") +@Controller('products') export class ProductController { constructor(private productService: ProductsService) {} @Get() public getProducts() { - return this.productService.getProducts(); + return this.productService.getProducts() } } ``` + We've applied the `getProducts` method with the `@Get` decorator which means when we make a `GET` request to `/products`, the method will be run. The method itself reaches into the service to get the data. Before we can test out this endpoint, we need to add `ProductsController` and `ProductsService` in the main module for the `api`. Open up `app.module.ts` found within `apps/api/src/app` and import `ProductsController` and `ProductsService`. Then include them in the `controllers` and `providers` arrays respectively. + + ```ts // apps/api/src/app/app.module.ts -import { Module } from "@nestjs/common"; +import { Module } from '@nestjs/common' -import { AppController } from "./app.controller"; -import { AppService } from "./app.service"; -import { ProductsController, ProductsService } from "@shirt-shop/products"; +import { AppController } from './app.controller' +import { AppService } from './app.service' +import { ProductsController, ProductsService } from '@shirt-shop/products' @Module({ imports: [], @@ -253,6 +297,7 @@ import { ProductsController, ProductsService } from "@shirt-shop/products"; export class AppModule {} ``` + Now head over to the browser and test it out by going to `http://localhost:3333/api/products`. ![Products data from the API](/full-stack-typesafety-with-angular-nest-nx-and-prisma-CcMK7fbQfTWc/imgs/nx-prisma-7.png) @@ -271,64 +316,78 @@ Instead of setting up a proxy for this demo, we can instead enable CORS on the b Open up `apps/api/src/main.ts` and add a call to `app.enableCors(); + + ```ts // apps/api/src/main.ts -import { Logger } from "@nestjs/common"; -import { NestFactory } from "@nestjs/core"; +import { Logger } from '@nestjs/common' +import { NestFactory } from '@nestjs/core' -import { AppModule } from "./app/app.module"; +import { AppModule } from './app/app.module' async function bootstrap() { - const app = await NestFactory.create(AppModule); - const globalPrefix = "api"; - app.setGlobalPrefix(globalPrefix); - app.enableCors(); - const port = process.env.PORT || 3333; + const app = await NestFactory.create(AppModule) + const globalPrefix = 'api' + app.setGlobalPrefix(globalPrefix) + app.enableCors() + const port = process.env.PORT || 3333 await app.listen(port, () => { - Logger.log("Listening at http://localhost:" + port + "/" + globalPrefix); - }); + Logger.log('Listening at http://localhost:' + port + '/' + globalPrefix) + }) } -bootstrap(); +bootstrap() ``` + ## Create a UI Module for the Angular App We could just start building components directly within the `shirt-shop` app in our Nx workspace, but that would be against the advice that Nx gives about how to manage code in our monorepos. Instead, let's create a new module that will be dedicated to components that make up our UI. Head over to the command line and create a new module. Follow the prompts to select the desired CSS variety. + + ```shell nx generate @nrwl/angular:lib ui ``` + Once the module is in place, we can create a component to list our products as well as a service to make the API call to get the data. Let's start by generating a component. + + ```shell nx g component products --project=ui --export ``` + Using the `--project=ui` flag tells Nx that we want to put this component in our newly-created `ui` module. We can see the result under `/libs/ui/src/lib/products`. Let's now create a service. + + ```shell nx g service product --project=ui --export ``` + With the new `UiModule` in place, we now need to add it to the `imports` array in our `app.module.ts` file for the frontend. + + ```ts // apps/shirt-shop/src/app/app.module.ts -import { BrowserModule } from "@angular/platform-browser"; -import { NgModule } from "@angular/core"; +import { BrowserModule } from '@angular/platform-browser' +import { NgModule } from '@angular/core' -import { AppComponent } from "./app.component"; -import { UiModule } from "@shirt-shop/ui"; +import { AppComponent } from './app.component' +import { UiModule } from '@shirt-shop/ui' @NgModule({ declarations: [AppComponent], @@ -339,19 +398,22 @@ import { UiModule } from "@shirt-shop/ui"; export class AppModule {} ``` + **Note:** If you get any errors saying that `@shirt-shop/ui` cannot be found, try restarting the front end by stopping that process and running `nx serve` again. ## Add an API Call to the Service We'll use Angular's built-in `HttpClientModule` to get access to an HTTP client for making requests to the API. To get started, let's import the appropriate module. The place to do this is within the `ui.module.ts` file in our new `UiModule`. + + ```ts // libs/ui/src/lib/ui.module.ts -import { NgModule } from "@angular/core"; -import { CommonModule } from "@angular/common"; -import { ProductsComponent } from "./products/products.component"; -import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from '@angular/core' +import { CommonModule } from '@angular/common' +import { ProductsComponent } from './products/products.component' +import { HttpClientModule } from '@angular/common/http' @NgModule({ imports: [CommonModule, HttpClientModule], @@ -361,32 +423,36 @@ import { HttpClientModule } from "@angular/common/http"; export class UiModule {} ``` + We can now import Angular's `HttpClient` within our `ProductService` and make calls with it. + + ```ts // libs/ui/src/lib/product.service.ts -import { HttpClient } from "@angular/common/http"; -import { Injectable } from "@angular/core"; -import { Product } from "@prisma/client"; -import { Observable } from "rxjs"; +import { HttpClient } from '@angular/common/http' +import { Injectable } from '@angular/core' +import { Product } from '@prisma/client' +import { Observable } from 'rxjs' @Injectable({ - providedIn: "root", + providedIn: 'root', }) export class ProductService { - private API_URL: string = "http://localhost:3333/api"; + private API_URL: string = 'http://localhost:3333/api' constructor(private readonly http: HttpClient) {} public getProducts(): Observable { { - return this.http.get(`${this.API_URL}/products`); + return this.http.get(`${this.API_URL}/products`) } } } ``` + Notice that we're using the same `Product` type that gets exported from `@prisma/client` here within our `ProductService` that was used on the backend in the `ProductsController`. This is a great illustration of how we can benefit from using the same types across our whole stack. When we use the `getProducts` method from this service, we'll now have type safety applied. ## Build Out the Products Component @@ -397,6 +463,7 @@ Let's start by adding some CSS that will style our component. Open up `libs/ui/src/lib/products/product.component.css` and add the following styles: + ``` /* libs/ui/src/lib/products/product.component.css */ @@ -447,8 +514,11 @@ Open up `libs/ui/src/lib/products/product.component.css` and add the following s } ``` + Next, open up `libs/ui/src/lib/products/product.component.html` and add the structure for products to be displayed.. + + ```html
@@ -461,32 +531,36 @@ Next, open up `libs/ui/src/lib/products/product.component.html` and add the stru
``` + Finally, we need to add a method to the component class which uses the `ProductService` to get the data. We'll then put the result on the `$products` observable that we've already stubbed out in our template above. + + ```ts // libs/ui/src/lib/products/products.component.ts -import { Component, OnInit } from "@angular/core"; -import { ProductService } from "../product.service"; -import { Observable } from "rxjs"; -import { Product } from "@prisma/client"; +import { Component, OnInit } from '@angular/core' +import { ProductService } from '../product.service' +import { Observable } from 'rxjs' +import { Product } from '@prisma/client' @Component({ - selector: "shirt-shop-products", - templateUrl: "./products.component.html", - styleUrls: ["./products.component.css"], + selector: 'shirt-shop-products', + templateUrl: './products.component.html', + styleUrls: ['./products.component.css'], }) export class ProductsComponent implements OnInit { - public $products: Observable; + public $products: Observable constructor(public productService: ProductService) {} ngOnInit(): void { - this.$products = this.productService.getProducts(); + this.$products = this.productService.getProducts() } } ``` + This is another spot where we're using our `Product` type from `@prisma/client` to give ourselves type safety. Applying this type directly to the `$products` observable means that we can get autocompletion in our Angular templates. ![Autocompletion in the Angular template](/full-stack-typesafety-with-angular-nest-nx-and-prisma-CcMK7fbQfTWc/imgs/nx-prisma-9.png) @@ -495,11 +569,14 @@ With our component in place, we're now ready to call it from the `shirt-shop` ap Open up `apps/shirt-shop/src/app/app.component.html` and include the `Products` component. + + ```html

Welcome to Shirt Shop!

``` + ![Products from ShirtShop](/full-stack-typesafety-with-angular-nest-nx-and-prisma-CcMK7fbQfTWc/imgs/nx-prisma-10.png) ## Going Beyond Displaying Data @@ -510,11 +587,13 @@ We won't build out a full CRUD experience for this demonstration, but we can tak Let's say we have a section in our app which allows admins to add new products in. We'd likely want to start by creating an endpoint to receive this data and store it. In this case, we could use the `create` method on `PrismaClient` along with the `ProductCreateInput` type that is exposed on a top-level export called `Prisma`. + + ```ts -import { Injectable } from "@nestjs/common"; -import { PrismaClient, Product, Prisma } from "@prisma/client"; +import { Injectable } from '@nestjs/common' +import { PrismaClient, Product, Prisma } from '@prisma/client' -const prisma = new PrismaClient(); +const prisma = new PrismaClient() @Injectable() export class ProductService { @@ -523,11 +602,12 @@ export class ProductService { public createProduct(data: Prisma.ProductCreateInput): Promise { return prisma.product.create({ data, - }); + }) } } ``` + The `createProduct` method takes in some data which is type-hinted to abide by the `Product` model from our Prisma schema. The returned result is a single `Product` that gets resolved from a `Promise`. It should be noted that just type-hinting our `data` parameter here doesn't do anything to add real validation to this endpoint. For data validation at the endpoint, we need to use [Validation Pipes](https://docs.nestjs.com/techniques/validation) from NestJS. @@ -537,3 +617,4 @@ It should be noted that just type-hinting our `data` parameter here doesn't do a TypeScript has come a long way since its early days and early adoption in the Angular community. Using TypeScript on both the frontend and backend bodes well for developer experience and confidence. Applying type safety to database access goes one step further in providing teams large and small with a slew of benefits. Wrapping the whole application up in a monorepo like those provided by Nx gives us an easy way of reusing code (including type definitions) across the whole stack. If you'd like to go even further with Prisma, [check out the docs](https://www.prisma.io/docs), follow us on [X/Twitter](https://pris.ly/x), and join our [Slack community](https://slack.prisma.io/)! + diff --git a/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-2-fwpc6ds155/index.mdx b/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-2-fwpc6ds155/index.mdx index 784194b783..13618bd665 100644 --- a/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-2-fwpc6ds155/index.mdx +++ b/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-2-fwpc6ds155/index.mdx @@ -83,7 +83,6 @@ To get started, navigate into the directory of your choice and run the following ```shell git clone -b part-2 https://github.com/m-abdelwahab/awesome-links.git ``` - You can now navigate into the cloned directory, install the dependencies and start the development server: ```shell @@ -91,7 +90,6 @@ cd awesome-links npm install npm run dev ``` - The app will be running at [`http://localhost:3000/`](http://localhost:3000/) and you will see four items. The data is hardcoded and comes from the `/data/links.ts` file. ![What the starter project looks like](/fullstack-nextjs-graphql-prisma-2-fwpc6ds155/imgs/awesome-links-starter-project.png) @@ -103,19 +101,16 @@ After setting up a PostgreSQL database, rename the `env.example` file to `.env` ```shell npx prisma migrate dev --name init ``` - If `prisma migrate dev` did not trigger the seed step, run the following command to seed the database: ```shell npx prisma db seed ``` - This command will run the `seed.ts` script, located in the `/prisma` directory. This script adds four links and one user to your database using Prisma Client. ### A look at the project structure and dependencies You will see the following folder structure - ``` awesome-links/ ┣ components/ @@ -144,7 +139,6 @@ awesome-links/ ┣ tailwind.config.js ┗ tsconfig.json ``` - This is a Next.js application with TailwindCSS set up along with Prisma. In the `pages` directory, you will find three files: @@ -177,11 +171,11 @@ While REST APIs offer advantages, they also have some drawbacks. We will use `aw Here is one possible way of structuring the REST API of `awesome-links`: | Resource | HTTP Method | Route | Description | -| -------- | ---------------------- | ------------ | ---------------------------------------------------------------------- | --- | +| -------- | ---------------------- | ------------ | ---------------------------------------------------------------------- | | `User` | `GET` | `/users` | returns all users and their information | | `User` | `GET` | `/users/:id` | returns a single user | | `Link` | `GET` | `/links` | returns all links | -| `Link` | `GET`, `PUT`, `DELETE` | `/links/:id` | returns a single link, updates it or deletes it. `id` is the link's id | | +| `Link` | `GET`, `PUT`, `DELETE` | `/links/:id` | returns a single link, updates it or deletes it. `id` is the link's id | | | `User` | `GET` | `/favorites` | returns a user's bookmarked links | | `User` | `POST` | `/link/save` | adds a link to the user's favorites | | `Link` | `POST` | `/link/new` | creates a new link (done by admin) | @@ -237,7 +231,6 @@ query { } } ``` - ![Example of GraphQL query](/fullstack-nextjs-graphql-prisma-2-fwpc6ds155/imgs/graphiql-example-query.png) The API only returns the `id` and `title`, even though a link has more fields. @@ -274,7 +267,6 @@ enum Role { USER } ``` - The `User` type has the following fields: - `id`, which is of type `ID`. @@ -296,7 +288,6 @@ type Link { users: [User] } ``` - This is a many-to-many relation between the `Link` and `User` object types since a `Link` can have many users, and a `User` can have many links. This is modeled in the database using Prisma. ### Defining Queries @@ -311,7 +302,6 @@ type Query { links: [Link]! } ``` - The `links` query returns an array of type `Link`. The `!` is used to indicate that this field is non-nullable, meaning that the API will always return a value when this field is queried. You can add more queries depending on the type of API you want to build. For the "awesome-links" app, you can add a query to return a single link, another one to return a single user, and another to return all users. @@ -324,7 +314,6 @@ type Query { users: [User]! } ``` - - The `link` query takes an argument `id` of type `ID` and returns a `Link`. The `id` argument is required, and the response is non-nullable. - The `user` query takes an argument `id` of type `ID` and returns a `User`. The `id` argument is required, and the response is non-nullable. - The `users` query returns an array of type `User`. The `id` argument is required. The response is non-nullable. @@ -337,25 +326,11 @@ For the "awesome-links" app, you will need different mutations for creating, upd ```graphql type Mutation { - createLink( - category: String! - description: String! - imageUrl: String! - title: String! - url: String! - ): Link! + createLink(category: String!, description: String!, imageUrl: String!, title: String!, url: String!): Link! deleteLink(id: ID!): Link! - updateLink( - category: String - description: String - id: String - imageUrl: String - title: String - url: String - ): Link! + updateLink(category: String, description: String, id: String, imageUrl: String, title: String, url: String): Link! } ``` - - The `createLink` mutation takes as an argument a `category`, a `description`, a `title`, a `url` and an `imageUrl`. All of these fields are of type `String` and are required. This mutation returns a `Link` object type. - The `deleteLink` mutation takes as an `id` of type `ID` as a required argument. It returns a required `Link`. - The `updateLink` mutation takes the same arguments as the `createLink` mutation. However, arguments are optional. This way, when updating a `Link` you will only pass the fields you want to be updated. This mutation returns a required `Link`. @@ -377,7 +352,6 @@ To get started, in the starter repo you cloned in the beginning, run the followi ```shell npm install graphql graphql-yoga ``` - The `graphql` package is the JavaScript reference implementation for GraphQL. It is a peer-dependency for `graphql-yoga`. ### Defining the schema of the app @@ -401,9 +375,8 @@ export const typeDefs = ` type Query { links: [Link]! } -`; +` ``` - ### Defining resolvers The next thing you need to do is create the resolver function for the `links` query. To do so, create a `/graphql/resolvers.ts` file and add the following code: @@ -415,35 +388,34 @@ export const resolvers = { links: () => { return [ { - category: "Open Source", - description: "Fullstack React framework", + category: 'Open Source', + description: 'Fullstack React framework', id: 1, - imageUrl: "https://nextjs.org/static/twitter-cards/home.png", - title: "Next.js", - url: "https://nextjs.org", + imageUrl: 'https://nextjs.org/static/twitter-cards/home.png', + title: 'Next.js', + url: 'https://nextjs.org', }, { - category: "Open Source", - description: "Next Generation ORM for TypeScript and JavaScript", + category: 'Open Source', + description: 'Next Generation ORM for TypeScript and JavaScript', id: 2, - imageUrl: "https://www.prisma.io/images/og-image.png", - title: "Prisma", - url: "https://www.prisma.io", + imageUrl: 'https://www.prisma.io/images/og-image.png', + title: 'Prisma', + url: 'https://www.prisma.io', }, { - category: "Open Source", - description: "GraphQL implementation", + category: 'Open Source', + description: 'GraphQL implementation', id: 3, - imageUrl: "https://www.apollographql.com/apollo-home.png", - title: "Apollo GraphQL", - url: "https://apollographql.com", + imageUrl: 'https://www.apollographql.com/apollo-home.png', + title: 'Apollo GraphQL', + url: 'https://apollographql.com', }, - ]; + ] }, }, -}; +} ``` - `resolvers` is an object where you will define the implementation for each query and mutation. The functions inside the `Query` object must match the names of the queries defined in the schema. Same thing goes for mutations. Here the `links` resolver function returns an array of objects, where each object is of type `Link`. @@ -456,29 +428,29 @@ Go ahead and create a `/pages/api/graphql.ts` file and add the following code: ```ts // pages/api/graphql.ts -import { createSchema, createYoga } from "graphql-yoga"; -import type { NextApiRequest, NextApiResponse } from "next"; -import { resolvers } from "../../graphql/resolvers"; -import { typeDefs } from "../../graphql/schema"; +import { createSchema, createYoga } from 'graphql-yoga' +import type { NextApiRequest, NextApiResponse } from 'next' +import { resolvers } from '../../graphql/resolvers' +import { typeDefs } from '../../graphql/schema' + export default createYoga<{ - req: NextApiRequest; - res: NextApiResponse; + req: NextApiRequest + res: NextApiResponse }>({ schema: createSchema({ typeDefs, - resolvers, + resolvers }), - graphqlEndpoint: "/api/graphql", -}); + graphqlEndpoint: '/api/graphql' +}) export const config = { api: { - bodyParser: false, - }, -}; + bodyParser: false + } +} ``` - You created a new GraphQL Yoga server instance that is the default export. You also created a schema using the `createSchema` function that takes the type-definitions and resolvers as a parameter. You then specified the path for the GraphQL API with the `graphqlEndpoint` property to `/api/graphql`. @@ -492,7 +464,6 @@ After completing the previous steps, start the server by running the following c ```shell npm run dev ``` - When you navigate to [`http://localhost:3000/api/graphql/`](http://localhost:3000/api/graphql/), you should see the following page: ![GraphiQL Playground for running queries](/fullstack-nextjs-graphql-prisma-2-fwpc6ds155/imgs/graphiql-playground.png) @@ -510,7 +481,6 @@ query { } } ``` - ![GraphiQL Playground with an example query](/fullstack-nextjs-graphql-prisma-2-fwpc6ds155/imgs/graphiql-documentation-explorer.png) The responses should be visible on the left panel, similar to the screenshot above. @@ -527,25 +497,24 @@ Prisma Client is an auto-generated, type-safe, query builder. To be able to use ```ts // /lib/prisma.ts -import { PrismaClient } from "@prisma/client"; +import { PrismaClient } from '@prisma/client' -let prisma: PrismaClient; +let prisma: PrismaClient declare global { var prisma: PrismaClient; } -if (process.env.NODE_ENV === "production") { - prisma = new PrismaClient(); +if (process.env.NODE_ENV === 'production') { + prisma = new PrismaClient() } else { if (!global.prisma) { - global.prisma = new PrismaClient(); + global.prisma = new PrismaClient() } - prisma = global.prisma; + prisma = global.prisma } -export default prisma; +export default prisma ``` - First, you are creating a new Prisma Client instance. Then if you are not in a production environment, Prisma will be attached to the global object so that you do not exhaust the database connection limit. For more details, check out the documentation for [Next.js and Prisma Client best practices](https://www.prisma.io/docs/guides/other/troubleshooting-orm/help-articles/nextjs-prisma-client-dev-practices). ### Query the database using Prisma @@ -554,16 +523,15 @@ Now you can update the resolver to return data from the database. Inside the `/g ```ts // /graphql/resolvers.ts -import prisma from "../lib/prisma"; +import prisma from '../lib/prisma' export const resolvers = { Query: { links: () => { - return prisma.link.findMany(); + return prisma.link.findMany() }, }, -}; +} ``` - If everything is set up correctly, when you go to GraphiQL,at [`http://localhost:3000/api/graphql`](http://localhost:3000/api/graphql) and re-run the links query, the data should be retrieved from your database. ## The flaws with our current GraphQL setup @@ -594,7 +562,6 @@ To get started, run the following command to install Pothos and the Prisma plugi ```shell npm install @pothos/plugin-prisma @pothos/core ``` - Next, add the `pothos` generator block to your Prisma schema right below the `client` generator: ```prisma @@ -608,13 +575,11 @@ generator client { + provider = "prisma-pothos-types" +} ``` - Run the following command to re-generate Prisma Client and Pothos types: ```sh npx prisma generate ``` - Next, create an instance of the Pothos schema builder as a shareable module. Inside the `graphql` folder, create a new file called `builder.ts` and add the following snippet: ```ts @@ -622,23 +587,23 @@ Next, create an instance of the Pothos schema builder as a shareable module. Ins // 1. import SchemaBuilder from "@pothos/core"; -import PrismaPlugin from "@pothos/plugin-prisma"; -import type PrismaTypes from "@pothos/plugin-prisma/generated"; +import PrismaPlugin from '@pothos/plugin-prisma'; +import type PrismaTypes from '@pothos/plugin-prisma/generated'; import prisma from "../lib/prisma"; -// 2. +// 2. export const builder = new SchemaBuilder<{ - // 3. - PrismaTypes: PrismaTypes; + // 3. + PrismaTypes: PrismaTypes }>({ // 4. plugins: [PrismaPlugin], prisma: { client: prisma, - }, -}); + } +}) -// 5. +// 5. builder.queryType({ fields: (t) => ({ ok: t.boolean({ @@ -647,7 +612,6 @@ builder.queryType({ }), }); ``` - 1. Defines all the libraries and utilities that will be needed 1. Creates a new `SchemaBuilder` instance 1. Defines the static types that will be used in creating the GraphQL schema @@ -661,9 +625,8 @@ Next, in the `/graphql/schema.ts` file replace the `typeDefs` with the following import { builder } from "./builder"; -export const schema = builder.toSchema(); +export const schema = builder.toSchema() ``` - Finally, update the import in the `/pages/api/graphql.ts` file: ```ts @@ -694,7 +657,6 @@ export const config = { } } ``` - ```ts @@ -732,25 +694,24 @@ The first step is defining a `Link` object type using Pothos. Go ahead and creat // /graphql/types/Link.ts import { builder } from "../builder"; -builder.prismaObject("Link", { +builder.prismaObject('Link', { fields: (t) => ({ - id: t.exposeID("id"), - title: t.exposeString("title"), - url: t.exposeString("url"), - description: t.exposeString("description"), - imageUrl: t.exposeString("imageUrl"), - category: t.exposeString("category"), - users: t.relation("users"), - }), -}); + id: t.exposeID('id'), + title: t.exposeString('title'), + url: t.exposeString('url'), + description: t.exposeString('description'), + imageUrl: t.exposeString('imageUrl'), + category: t.exposeString('category'), + users: t.relation('users') + }) +}) ``` - Since you're using the Pothos' Prisma plugin, the `builder` instance provides utility methods for defining your GraphQL schema such as [`prismaObject`](https://pothos-graphql.dev/docs/plugins/prisma#creating-types-with-builderprismaobject). -`prismaObject` accepts two arguments: +`prismaObject` accepts two arguments: - `name`: The name of the model in the Prisma schema you would like to _expose_. -- `options`: The options for defining the type you're exposing such as the description, fields, etc. +- `options`: The options for defining the type you're exposing such as the description, fields, etc. > **Note**: You can use CTRL + Space to invoke your editor's intellisense and view the available arguments. @@ -764,21 +725,20 @@ Now create a new `/graphql/types/User.ts` file and add the following to code to // /graphql/types/User.ts import { builder } from "../builder"; -builder.prismaObject("User", { +builder.prismaObject('User', { fields: (t) => ({ - id: t.exposeID("id"), - email: t.exposeString("email", { nullable: true }), - image: t.exposeString("image", { nullable: true }), - role: t.expose("role", { type: Role }), - bookmarks: t.relation("bookmarks"), - }), -}); + id: t.exposeID('id'), + email: t.exposeString('email', { nullable: true, }), + image: t.exposeString('image', { nullable: true, }), + role: t.expose('role', { type: Role, }), + bookmarks: t.relation('bookmarks'), + }) +}) -const Role = builder.enumType("Role", { - values: ["USER", "ADMIN"] as const, -}); +const Role = builder.enumType('Role', { + values: ['USER', 'ADMIN'] as const, +}) ``` - Since the `email` and `image` fields in the Prisma schema are nullable, pass the `nullable: true` as a second argument to the expose method. The default type for the `role` field when "exposing" it's type from the generated schema. In the above example, you've defined an explicit enum type called `Role` which is then used to resolve the `role`'s field type. @@ -794,7 +754,6 @@ import { builder } from "./builder"; export const schema = builder.toSchema() ``` - ### Defining queries using Pothos In the `graphql/types/Link.ts` file, add the following code below the `Link` object type definition: @@ -803,24 +762,23 @@ In the `graphql/types/Link.ts` file, add the following code below the `Link` obj // graphql/types/Link.ts // code above unchanged -// 1. +// 1. builder.queryField("links", (t) => - // 2. +// 2. t.prismaField({ - // 3. - type: ["Link"], - // 4. - resolve: (query, _parent, _args, _ctx, _info) => prisma.link.findMany({ ...query }), - }), -); + // 3. + type: ['Link'], + // 4. + resolve: (query, _parent, _args, _ctx, _info) => + prisma.link.findMany({ ...query }) + }) +) ``` - In the above snippet: - 1. Defines a query type called `links`. 1. Defines the field that will resolve to the generated Prisma Client types. 1. Specifies the field that Pothos will use to resolve the field. In this case, it resolves to an array of the `Link` type -1. Defines the logic for the query. +1. Defines the logic for the query. The `query` argument in the resolver function adds a `select` or `include` to your query to resolve as many relation fields as possible in a single request. @@ -843,21 +801,19 @@ To get started with Apollo Client, add to your project by running the following ```shell npm install @apollo/client ``` - Next, in the `/lib` directory create a new file called `apollo.ts` and add the following code to it: ```ts // /lib/apollo.ts -import { ApolloClient, InMemoryCache } from "@apollo/client"; +import { ApolloClient, InMemoryCache } from '@apollo/client' const apolloClient = new ApolloClient({ - uri: "/api/graphql", + uri: '/api/graphql', cache: new InMemoryCache(), -}); +}) -export default apolloClient; +export default apolloClient ``` - You are creating a new `ApolloClient` instance to which you are passing a configuration object with `uri` and `cache` fields. - The `uri` field specifies the GraphQL endpoint you will interact with. This will be changed to the production URL when the app is deployed. @@ -885,7 +841,6 @@ function MyApp({ Component, pageProps }: AppProps) { export default MyApp ``` - You are wrapping the global `App` component with the Apollo Provider so all of the project's components can send GraphQL queries. > **Note**: Next.js supports different data fetching strategies. You can fetch data server-side, client-side, or at build-time. To support pagination, you need to fetch data client-side. @@ -896,9 +851,9 @@ To load data on your frontend using Apollo client, update the `/pages/index.tsx` ```tsx // /pages/index.tsx -import Head from "next/head"; -import { gql, useQuery } from "@apollo/client"; -import type { Link } from "@prisma/client"; +import Head from 'next/head' +import { gql, useQuery } from '@apollo/client' +import type { Link } from '@prisma/client' const AllLinksQuery = gql` query { @@ -911,13 +866,13 @@ const AllLinksQuery = gql` category } } -`; +` export default function Home() { - const { data, loading, error } = useQuery(AllLinksQuery); + const { data, loading, error } = useQuery(AllLinksQuery) - if (loading) return

Loading...

; - if (error) return

Oh no... {error.message}

; + if (loading) return

Loading...

+ if (error) return

Oh no... {error.message}

return (
@@ -936,7 +891,7 @@ export default function Home() {

{link.title}

{link.description}

- {link.url.replace(/(^\w+:|^)\/\//, "")} + {link.url.replace(/(^\w+:|^)\/\//, '')}
- ); + ) } ``` - You are using the `useQuery` hook to send queries to the GraphQL endpoint. This hook has a required parameter of a GraphQL query string. When the component renders, `useQuery` returns an object which contains three values: - `loading`: a boolean that determines whether or not the data has been returned. @@ -1019,7 +973,6 @@ query allLinksQuery($first: Int, $after: ID) { } } ``` - The query takes two arguments, `first` and `after`: - `first`: an `Int` that specifies how many items you want the API to return. @@ -1053,7 +1006,6 @@ Install the plugin with the following command: ```sh npm install @pothos/plugin-relay ``` - Update the `graphql/builder.ts` to include the relay plugin. ```ts @@ -1085,7 +1037,6 @@ builder.queryType({ }), }); ``` - ### Updating the resolver to return paginated data from the database To use cursor-based pagination make the following update to the `links` query: @@ -1106,8 +1057,7 @@ builder.queryField('links', (t) => }) ) ``` - -The `prismaConnection` method is used to create a `connection` field that also pre-loads the data inside that connection. +The `prismaConnection` method is used to create a `connection` field that also pre-loads the data inside that connection. @@ -1116,27 +1066,27 @@ The `prismaConnection` method is used to create a `connection` field that also p import { builder } from "../builder"; builder.prismaObject('Link', { -fields: (t) => ({ -id: t.exposeID('id'), -title: t.exposeString('title'), -url: t.exposeString('url'), -description: t.exposeString('description'), -imageUrl: t.exposeString('imageUrl'), -category: t.exposeString('category'), -users: t.relation('users') -}), + fields: (t) => ({ + id: t.exposeID('id'), + title: t.exposeString('title'), + url: t.exposeString('url'), + description: t.exposeString('description'), + imageUrl: t.exposeString('imageUrl'), + category: t.exposeString('category'), + users: t.relation('users') + }), }) + builder.queryField('links', (t) => -t.prismaConnection({ -type: 'Link', -cursor: 'id', -resolve: (query, \_parent, \_args, \_ctx, \_info) => -prisma.link.findMany({ ...query }) -}) + t.prismaConnection({ + type: 'Link', + cursor: 'id', + resolve: (query, _parent, _args, _ctx, _info) => + prisma.link.findMany({ ...query }) + }) ) - -```` +``` @@ -1148,7 +1098,7 @@ Here is a diagram that summarizes how pagination works on the server: Now that the API supports pagination, you can fetch paginated data on the client using Apollo Client. -The `useQuery` hook returns an object containing `data`, `loading` and `errors`. However, `useQuery` also returns a `fetchMore()` function, which is used to handle pagination and updating the UI when a result is returned. +The `useQuery` hook returns an object containing `data`, `loading` and `errors`. However, `useQuery` also returns a `fetchMore()` function, which is used to handle pagination and updating the UI when a result is returned. Navigate to the `/pages/index.tsx` file and update it to use the following code to add support for pagination: ```tsx @@ -1239,8 +1189,7 @@ function Home() { } export default Home; -```` - +``` You are first passing a `variables` object to the `useQuery` hook, which contains a key called `first` with a value of `2`. This means you will be fetching two links. You can set this value to any number you want. The `data` variable will contain the data returned from the initial request to the API. @@ -1274,3 +1223,4 @@ In [the next part](/fullstack-nextjs-graphql-prisma-3-clxbrcqppv) of the course, - Create an admin-only route for creating links - Set up AWS S3 to handle file uploads - Add a mutation to create links as an admin + diff --git a/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-3-clxbrcqppv/index.mdx b/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-3-clxbrcqppv/index.mdx index 9b211a8b77..eeb9e078c4 100644 --- a/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-3-clxbrcqppv/index.mdx +++ b/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-3-clxbrcqppv/index.mdx @@ -18,7 +18,7 @@ tags: --- This article is the third part of the course where you build a fullstack app with Next.js, GraphQL, TypeScript, Prisma -and PostgreSQL. In this article, you will learn how to add authentication to your app. + and PostgreSQL. In this article, you will learn how to add authentication to your app. ## Table of Contents @@ -63,14 +63,12 @@ To get started, navigate into the directory of your choice and run the following ```shell git clone -b part-3 https://github.com/prisma/awesome-links.git ``` - Navigate into the cloned application and install the dependencies: ```shell cd awesome-links npm install ``` - ## Seed the database After setting up a PostgreSQL database, rename the `env.example` file to `.env` and set the connection string for your database. After that, run the following command to create the tables in your database: @@ -80,13 +78,11 @@ After setting up a PostgreSQL database, rename the `env.example` file to `.env` ```shell npx prisma migrate dev --name init ``` - If `prisma migrate dev` did not trigger the seed step, run the following command to seed the database: ```shell npx prisma db seed ``` - This command will run the `seed.ts` file in the `/prisma` directory. `seed.ts` creates four links and one user in your database using Prisma Client. You can now start the application server by running the following command: @@ -94,7 +90,6 @@ You can now start the application server by running the following command: ```shell npm run dev ``` - ### Project structure and dependencies The project has the following folder structure: @@ -136,7 +131,6 @@ awesome-links/ ┣ tailwind.config.js ┗ tsconfig.json ``` - This is a Next.js application that uses the following libraries and tools: - [Prisma](https://www.prisma.io) for database access/CRUD operations @@ -149,7 +143,7 @@ This is a Next.js application that uses the following libraries and tools: The `pages` directory contains the following files: - `index.tsx`: fetches links from the API and displays them on the page. The results are paginated and you can fetch more links. -- `_app.tsx`: root component that allows you to persist layouts and state when navigating between pages. +- `_app.tsx`: root component that allows you to persist layouts and state when navigating between pages. - `/api/graphql.ts`: GraphQL endpoint using Next.js's [API routes](https://nextjs.org/docs/pages/building-your-application/routing/api-routes). ## Authentication and securing the GraphQL API using Auth0 @@ -182,7 +176,6 @@ AUTH0_ISSUER_BASE_URL='https://YOUR_APP_DOMAIN' AUTH0_CLIENT_ID='YOUR_CLIENT_ID' AUTH0_CLIENT_SECRET='YOUR_CLIENT_SECRET' ``` - - `AUTH0_SECRET`: A long secret value used to encrypt the session cookie. You can generate a suitable string by running `openssl rand -hex 32` in your terminal. - `AUTH0_BASE_URL`: The base URL of your application. - `AUTH0_ISSUER_BASE_URL`: The URL of your Auth0 tenant domain. @@ -204,16 +197,14 @@ You can add Auth0 to your project by installing the [Auth0 Next.js SDK](https:// ```shell npm install @auth0/nextjs-auth0 ``` - Next, create an `auth/[...auth0].ts` file inside the `pages/api` directory and add the following code to it: ```ts // pages/api/auth/[...auth0].ts -import { handleAuth } from "@auth0/nextjs-auth0"; +import { handleAuth } from '@auth0/nextjs-auth0' -export default handleAuth(); +export default handleAuth() ``` - This [Next.js dynamic API route](https://nextjs.org/docs/routing/dynamic-routes#catch-all-routes) will automatically create the following endpoints: - `/api/auth/login`: Auth0's login route. @@ -225,12 +216,12 @@ Finally, navigate to the `pages/_app.tsx` file and update it with the following ```tsx // pages/_app.tsx -import "../styles/tailwind.css"; -import { UserProvider } from "@auth0/nextjs-auth0/client"; -import Layout from "../components/Layout"; -import { ApolloProvider } from "@apollo/client"; -import type { AppProps } from "next/app"; -import apolloClient from "../lib/apollo"; +import '../styles/tailwind.css' +import { UserProvider } from '@auth0/nextjs-auth0/client' +import Layout from '../components/Layout' +import { ApolloProvider } from '@apollo/client' +import type { AppProps } from 'next/app' +import apolloClient from '../lib/apollo' function MyApp({ Component, pageProps }: AppProps) { return ( @@ -241,12 +232,11 @@ function MyApp({ Component, pageProps }: AppProps) { - ); + ) } -export default MyApp; +export default MyApp ``` - Wrapping the `MyApp` component with the `UserProvider` component will allow all pages to access your user's authentication state. ### Secure the GraphQL API @@ -257,26 +247,26 @@ Create a `graphql/context.ts` file and add the following snippet: ```ts // graphql/context.ts -import { getSession } from "@auth0/nextjs-auth0"; -import type { NextApiRequest, NextApiResponse } from "next"; +import { getSession } from '@auth0/nextjs-auth0' +import type { NextApiRequest, NextApiResponse } from 'next' -export async function createContext({ req, res }: { req: NextApiRequest; res: NextApiResponse }) { - const session = await getSession(req, res); +export async function createContext({ req, res }: { req: NextApiRequest, res: NextApiResponse }) { + const session = await getSession(req, res) // if the user is not logged in, return an empty object - if (!session || typeof session === "undefined") return {}; + if (!session || typeof session === 'undefined') return {} - const { user, accessToken } = session; + const { user, accessToken } = session return { user, accessToken, - }; + } } ``` - The `getSession()` function from Auth0 returns information about the logged-in user and the access token. This data is then included in the GraphQL context. Your queries and mutations can now access the authentication state. + Update the server instance with the `context` property with the `createContext` function as it's value: ```ts @@ -304,7 +294,6 @@ export const config = { } } ``` - Next, update the `SchemaBuilder` function in `graphql/builder.ts` by specifying the type for the `Context` object: ```ts @@ -336,24 +325,20 @@ builder.queryType({ }), }); ``` - Finally, the app's navbar should display a **Login**/**Logout** button depending on the user's authentication state. Update the `Header` component in `components/Layout/Header.tsx` with the following code: ```tsx // components/Layout/Header.tsx -import React from "react"; -import Link from "next/link"; -import { useUser } from "@auth0/nextjs-auth0/client"; +import React from 'react' +import Link from 'next/link' +import { useUser } from '@auth0/nextjs-auth0/client' const Header = () => { - const { user } = useUser(); + const { user } = useUser() return (
- + {
- + Logout - profile + profile
) : ( - + Login )}
- ); -}; + ) +} -export default Header; +export default Header ``` - The `useUser` hook from Auth0 checks whether a user is authenticated or not. This hook runs client-side. If you have done all the previous steps correctly, you should be able to sign up and login to the app! @@ -415,10 +389,11 @@ Auth0 only manages users on your behalf and doesn't allow storing any data excep To achieve that, you will leverage [Auth0 Actions](https://auth0.com/docs/actions). Auth0 Actions are serverless functions that can execute at certain points during the Auth0 runtime. -You will define an API route that will receive the information sent from the Auth0 Action during the login process and save the information to your database. This pattern of creating an API endpoint to listen to events from a third party service is called a [webhook](https://sendgrid.com/blog/whats-webhook/). +You will define an API route that will receive the information sent from the Auth0 Action during the login process and save the information to your database. This pattern of creating an API endpoint to listen to events from a third party service is called a [webhook](https://sendgrid.com/blog/whats-webhook/). To get started with Auth0 Actions, navigate to the **Actions** dropdown located in the left sidebar, select **Flows** and choose **Login**. + ![Auth0 Actions choose flow](/fullstack-nextjs-graphql-prisma-3-clxbrcqppv/imgs/auth0-actions-login.png) Next, to create a new Action, click the **+** icon and choose **Build custom**. @@ -441,17 +416,17 @@ Here is a breakdown of the Auth0 Actions UI: The first step is to include the `node-fetch` module version `2.6.1`. You will use it in your Action to send a request to an API endpoint – you will create this later. This endpoint will handle the logic of creating a user record in the database. + ![Include package in Auth0 Action](/fullstack-nextjs-graphql-prisma-3-clxbrcqppv/imgs/auth0-action-add-module.png) -Next, define a secret that will be included in every request sent by the Action to your endpoint. This secret will ensure that the requests are coming from the Auth0 Action instead of another untrusted third party. +Next, define a secret that will be included in every request sent by the Action to your endpoint. This secret will ensure that the requests are coming from the Auth0 Action instead of another untrusted third party. You can generate a random secret using the following command in your terminal: ```shell openssl rand -hex 32 ``` - -First, store this secret in the Auth0 dashboard with the key `AUTH0_HOOK_SECRET`. +First, store this secret in the Auth0 dashboard with the key `AUTH0_HOOK_SECRET`. ![Auth0 add environment variables](/fullstack-nextjs-graphql-prisma-3-clxbrcqppv/imgs/auth0-action-add-hook-secret.png) @@ -460,49 +435,46 @@ Now, also store the secret in your `.env` file. ```shell AUTH0_HOOK_SECRET='' # same secret goes here ``` - ![Auth0 add environment variables](/fullstack-nextjs-graphql-prisma-3-clxbrcqppv/imgs/auth0-action-add-hook-secret.png) Finally, update the Action with the following code: ```js -const fetch = require("node-fetch"); +const fetch = require('node-fetch') exports.onExecutePostLogin = async (event, api) => { - // 1. - const SECRET = event.secrets.AUTH0_HOOK_SECRET; - + // 1. + const SECRET = event.secrets.AUTH0_HOOK_SECRET + // 2. if (event.user.app_metadata.localUserCreated) { - return; + return } // 3. - const email = event.user.email; + const email = event.user.email // 4. - const request = await fetch("http://localhost:3000/api/auth/hook", { - // "localhost:3000" will be replaced before deploying this Action - method: "post", + const request = await fetch('http://localhost:3000/api/auth/hook', { // "localhost:3000" will be replaced before deploying this Action + method: 'post', body: JSON.stringify({ email, secret: SECRET }), - headers: { "Content-Type": "application/json" }, - }); - const response = await request.json(); + headers: { 'Content-Type': 'application/json' }, + }) + const response = await request.json() // 5. - api.user.setAppMetadata("localUserCreated", true); -}; + api.user.setAppMetadata('localUserCreated', true) +} ``` - 1. Retrieves the `AUTH0_HOOK_SECRET` environment variable 1. Checks if the `localUserCreated` property on the user's `app_metadata` 1. Retrieves user's email from the login event – provided by Auth0 1. Sends a `POST` request to an API route – `http://localhost:3000/api/auth/hook` 1. Adds the `localUserCreated` property to the user's `app_metadata` -The `api.user.setAppMetadata` function allows you to add additional properties to a user's profile. +The `api.user.setAppMetadata` function allows you to add additional properties to a user's profile. -Before you deploy this action, there's one more thing left to do. +Before you deploy this action, there's one more thing left to do. ### Expose `localhost:3000` using Ngrok @@ -515,9 +487,8 @@ TODO: sign up for an account, get token from the dashboard While your app is running, run the following command to expose `localhost:3000`: ```shell -npx ngrok http 3000 --authtoken "TOKEN" +npx ngrok http 3000 --authtoken "TOKEN" ``` - > **Note**: Make sure to replace the `TOKEN` value with the token from Ngrok's dashboard. The output on your terminal will resemble the following – but with different **Forwarding** URLs: @@ -526,7 +497,7 @@ The output on your terminal will resemble the following – but with different * Copy the **Forwarding** URL, replace `localhost:3000` with your **Forwarding** URL in your Action and click **Deploy**. -Now that the action is deployed, go back to the **Login** flow by pressing the **Back to flow** button. +Now that the action is deployed, go back to the **Login** flow by pressing the **Back to flow** button. The final thing you need to do is add your newly created action to the **Login** flow. You will find the action underneath the **Custom** tab. To add the action to your flow, you can drag-and-drop it between **Start** and **Complete**. Then click **Apply** to save the changes. @@ -538,14 +509,14 @@ Create a `hook.ts` file in the `pages/api/auth/` folder and add the following co ```ts // pages/api/auth/hook.ts -import prisma from "../../../lib/prisma"; -import type { NextApiRequest, NextApiResponse } from "next"; +import prisma from '../../../lib/prisma'; +import type { NextApiRequest, NextApiResponse } from 'next'; const handler = async (req: NextApiRequest, res: NextApiResponse) => { const { email, secret } = req.body; // 1 - if (req.method !== "POST") { - return res.status(403).json({ message: "Method not allowed" }); + if (req.method !== 'POST') { + return res.status(403).json({ message: 'Method not allowed' }); } // 2 if (secret !== process.env.AUTH0_HOOK_SECRET) { @@ -565,7 +536,6 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { export default handler; ``` - This endpoint does the following: 1. Validates the request is a `POST` request @@ -582,7 +552,7 @@ Once a user signs up to your application, the user's information will be synced Navigate to `graphql/builder.ts` file and update with the following snippet: ```ts -diff; +diff // graphql/builder.ts // ...code above remains unchanged @@ -594,9 +564,8 @@ builder.queryType({ }), }); -+builder.mutationType({}); ++builder.mutationType({}) ``` - The above snippet registeres the `Mutation` type in the schema which allows you to define mutations in your GraphQL server. Next, update `graphql/types/Link.ts` with the following mutation that adds the ability to create links: @@ -607,7 +576,7 @@ Next, update `graphql/types/Link.ts` with the following mutation that adds the a builder.mutationField("createLink", (t) => t.prismaField({ - type: "Link", + type: 'Link', args: { title: t.arg.string({ required: true }), description: t.arg.string({ required: true }), @@ -616,10 +585,10 @@ builder.mutationField("createLink", (t) => category: t.arg.string({ required: true }), }, resolve: async (query, _parent, args, ctx) => { - const { title, description, url, imageUrl, category } = args; + const { title, description, url, imageUrl, category } = args if (!(await ctx).user) { - throw new Error("You have to be logged in to perform this action"); + throw new Error("You have to be logged in to perform this action") } return prisma.link.create({ @@ -630,32 +599,30 @@ builder.mutationField("createLink", (t) => url, imageUrl, category, - }, - }); - }, - }), -); + } + }) + } + }) +) ``` - -The `args` property defines the input required to create a new link. The mutation also checks if a user is logged in so only authenticated users can create links. Finally, the `create()` function from Prisma creates a new database record. +The `args` property defines the input required to create a new link. The mutation also checks if a user is logged in so only authenticated users can create links. Finally, the `create()` function from Prisma creates a new database record. Install the following dependencies you'll use for form management and notifications: ```sh npm install react-hook-form react-hot-toast ``` - Next, create `pages/admin.tsx` page and add the following code. The code allows creation of a new link: ```tsx // pages/admin.tsx -import React from "react"; -import { type SubmitHandler, useForm } from "react-hook-form"; -import { gql, useMutation } from "@apollo/client"; -import toast, { Toaster } from "react-hot-toast"; -import { getSession } from "@auth0/nextjs-auth0"; -import prisma from "../lib/prisma"; -import type { GetServerSideProps } from "next"; +import React from 'react' +import { type SubmitHandler, useForm } from 'react-hook-form' +import { gql, useMutation } from '@apollo/client' +import toast, { Toaster } from 'react-hot-toast' +import { getSession } from '@auth0/nextjs-auth0' +import prisma from '../lib/prisma' +import type { GetServerSideProps } from 'next' type FormValues = { title: string; @@ -663,23 +630,11 @@ type FormValues = { category: string; description: string; image: FileList; -}; +} const CreateLinkMutation = gql` - mutation createLink( - $title: String! - $url: String! - $imageUrl: String! - $category: String! - $description: String! - ) { - createLink( - title: $title - url: $url - imageUrl: $imageUrl - category: $category - description: $description - ) { + mutation createLink($title: String!, $url: String!, $imageUrl: String!, $category: String!, $description: String!) { + createLink(title: $title, url: $url, imageUrl: $imageUrl, category: $category, description: $description) { title url imageUrl @@ -687,7 +642,7 @@ const CreateLinkMutation = gql` description } } -`; +` const Admin = () => { const { @@ -695,40 +650,38 @@ const Admin = () => { handleSubmit, formState: { errors }, reset, - } = useForm(); + } = useForm() const [createLink, { loading, error }] = useMutation(CreateLinkMutation, { - onCompleted: () => reset(), - }); + onCompleted: () => reset() + }) const onSubmit: SubmitHandler = async (data) => { - const { title, url, category, description } = data; - const imageUrl = `https://via.placeholder.com/300`; - const variables = { title, url, category, description, imageUrl }; + const { title, url, category, description } = data + const imageUrl = `https://via.placeholder.com/300` + const variables = { title, url, category, description, imageUrl } try { toast.promise(createLink({ variables }), { - loading: "Creating new link..", - success: "Link successfully created!🎉", + loading: 'Creating new link..', + success: 'Link successfully created!🎉', error: `Something went wrong 😥 Please try again - ${error}`, - }); + }) + } catch (error) { - console.error(error); + console.error(error) } - }; + } return (

Create a new link

-
+
- ); -}; + ) +} -export default Admin; +export default Admin export const getServerSideProps: GetServerSideProps = async ({ req, res }) => { const session = await getSession(req, res); @@ -800,18 +753,17 @@ export const getServerSideProps: GetServerSideProps = async ({ req, res }) => { return { redirect: { permanent: false, - destination: "/api/auth/login", + destination: '/api/auth/login', }, props: {}, - }; + } } return { props: {}, }; -}; +} ``` - The `onSubmit` function passes the form values to the `createLink` mutation. A toast will be shown as the mutation is being executed – success, loading, or error. In `getServerSideProps`, if there is no session, you are redirecting the user to the login page. If a user record that matches the email of the logged-in user is found, the `/admin` page is rendered. @@ -845,7 +797,6 @@ const Header = () => { export default Header ``` - You should now be able to create links! 🚀 ### Bonus: protecting pages based on the user role @@ -858,7 +809,7 @@ Firstly, update the `createLink` mutation to check a user's role: // graphql/types/Link.ts builder.mutationField("createLink", (t) => t.prismaField({ - type: "Link", + type: 'Link', args: { title: t.arg.string({ required: true }), description: t.arg.string({ required: true }), @@ -867,20 +818,20 @@ builder.mutationField("createLink", (t) => category: t.arg.string({ required: true }), }, resolve: async (query, _parent, args, ctx) => { - const { title, description, url, imageUrl, category } = args; + const { title, description, url, imageUrl, category } = args if (!(await ctx).user) { - throw new Error("You have to be logged in to perform this action"); + throw new Error("You have to be logged in to perform this action") } const user = await prisma.user.findUnique({ where: { email: (await ctx).user?.email, - }, - }); + } + }) if (!user || user.role !== "ADMIN") { - throw new Error("You don have permission ot perform this action"); + throw new Error("You don have permission ot perform this action") } return prisma.link.create({ @@ -891,13 +842,12 @@ builder.mutationField("createLink", (t) => url, imageUrl, category, - }, - }); - }, - }), -); + } + }) + } + }) +) ``` - Update `admin.tsx` page by adding the role check in your `getServerSideProps` to redirect users that are not admins. Users without the `ADMIN` role will be redirected to the `/404` page. ```tsx @@ -909,7 +859,7 @@ export const getServerSideProps: GetServerSideProps = async ({ req, res }) => { return { redirect: { permanent: false, - destination: "/api/auth/login", + destination: '/api/auth/login', }, props: {}, }; @@ -925,11 +875,11 @@ export const getServerSideProps: GetServerSideProps = async ({ req, res }) => { }, }); - if (!user || user.role !== "ADMIN") { + if (!user || user.role !== 'ADMIN') { return { redirect: { permanent: false, - destination: "/404", + destination: '/404', }, props: {}, }; @@ -940,12 +890,11 @@ export const getServerSideProps: GetServerSideProps = async ({ req, res }) => { }; }; ``` +The default role assigned to a user when signing up is `USER`. So if you try to go to the `/admin` page, it will no longer work. -The default role assigned to a user when signing up is `USER`. So if you try to go to the `/admin` page, it will no longer work. +You can change this by modifying the `role` field of the user in the database. This is very easy to do in Prisma Studio. -You can change this by modifying the `role` field of the user in the database. This is very easy to do in Prisma Studio. - -First start Prisma Studio by running `npx prisma studio` in the terminal. Then click the **User** model and find the record matching the current user. Now, go ahead and update your user role from `USER` to `ADMIN`. Save your changes by pressing the **Save 1 change** button. +First start Prisma Studio by running `npx prisma studio` in the terminal. Then click the **User** model and find the record matching the current user. Now, go ahead and update your user role from `USER` to `ADMIN`. Save your changes by pressing the **Save 1 change** button. ![Prisma Studio – update user role](/fullstack-nextjs-graphql-prisma-3-clxbrcqppv/imgs/prisma-studio-role-update.png) @@ -956,3 +905,4 @@ Navigate to the `/admin` page of your application and voila! You can now create In this part, you learned how to add authentication and authorization to a Next.js app using Auth0 and how you can use Auth0 Actions to add users to your database. Stay tuned for [the next part](/fullstack-nextjs-graphql-prisma-4-1k1kc83x3v) where you'll learn how to add image upload using AWS S3. + diff --git a/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-4-1k1kc83x3v/index.mdx b/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-4-1k1kc83x3v/index.mdx index 2486228919..ccbce97803 100644 --- a/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-4-1k1kc83x3v/index.mdx +++ b/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-4-1k1kc83x3v/index.mdx @@ -18,10 +18,9 @@ tags: --- This article is the fourth part of the course where you build a fullstack app with Next.js, GraphQL, TypeScript, -Prisma and PostgreSQL. In this article, you will learn how to add image upload using AWS S3. + Prisma and PostgreSQL. In this article, you will learn how to add image upload using AWS S3. ## Table of Contents - - [Introduction](#introduction) - [Development environment](#development-environment) - [Clone the repository](#clone-the-repository) @@ -29,9 +28,9 @@ Prisma and PostgreSQL. In this article, you will learn how to add image upload u - [Project structure and dependencies](#project-structure-and-dependencies) - [Using AWS S3 to add support for image upload](#using-aws-s3-to-add-support-for-image-upload) - [Create an Identity Access Management user - ](#create-an-identity-access-management-user) +](#create-an-identity-access-management-user) - [Create and configure a new S3 bucket - ](#create-and-configure-a-new-s3-bucket) +](#create-and-configure-a-new-s3-bucket) - [Add image upload functionality to your application](#add-image-upload-functionality-to-your-application) - [Summary and next steps](#summary-and-next-steps) @@ -62,14 +61,12 @@ To get started, navigate into the directory of your choice and run the following ```shell git clone -b part-4 https://github.com/prisma/awesome-links.git ``` - Navigate into the cloned application and install the dependencies: ```shell cd awesome-links npm install ``` - ## Seeding the database After setting up a PostgreSQL database, rename the `env.example` file to `.env` and set the connection string for your database. After that, run the following command to create the tables in your database: @@ -77,13 +74,11 @@ After setting up a PostgreSQL database, rename the `env.example` file to `.env` ```shell npx prisma migrate dev --name init ``` - If `prisma migrate dev` did not trigger the seed step, run the following command to seed the database: ```shell npx prisma db seed ``` - > Refer to [Part 1 – Add Prisma to your Project](/fullstack-nextjs-graphql-prisma-oklidw1rhw#add-prisma-to-your-project) for more details on the format of the connection string. This command will run the `seed.ts` file in the `/prisma` directory. `seed.ts` creates four links and one user in your database using Prisma Client. @@ -137,7 +132,6 @@ awesome-links/ ┣ tailwind.config.js ┗ tsconfig.json ``` - This is a Next.js application that uses the following libraries and tools: - [Prisma](https://www.prisma.io) for database access/CRUD operations @@ -208,7 +202,6 @@ Finally, copy the "Access Key ID" and the "Secret Access Key" and store them in APP_AWS_ACCESS_KEY = '' APP_AWS_SECRET_KEY = '' ``` - ### Create and configure a new S3 bucket The next step is to create an AWS S3 bucket which will store the uploaded objects. You can find the S3 service by looking it up in the search bar or by going to [https://s3.console.aws.amazon.com/](https://s3.console.aws.amazon.com/). @@ -228,7 +221,6 @@ APP_AWS_REGION = '' AWS_S3_BUCKET_NAME = '' # Will be used in an API route. NEXT_PUBLIC_AWS_S3_BUCKET_NAME = '' # Will be used on the client-side ``` - > Note: The bucket name has to be unique and must not contain any spaces or uppercase letters. Go ahead and create the bucket by navigating to the bottom of the page and clicking the **Create bucket** button. You can stick with the defaults settings for now, but you'll update them in the following steps. @@ -244,6 +236,7 @@ Uncheck **Block _all_ public access** and click on **Save changes**. You need to ![AWS S3](/fullstack-nextjs-graphql-prisma-4-1k1kc83x3v/imgs/aws-s3-access-settings.png) + Next, update the resource policy to grant the application access to the Bucket and its contents. In the **Permissions** of your S3 Bucket, navigate to the **Bucket policy** section. Select **Edit** and add the following while changing "name-of-your-bucket" placeholder to the name of your Bucket: ```json @@ -262,7 +255,6 @@ Next, update the resource policy to grant the application access to the Bucket a ] } ``` - ![AWS S3](/fullstack-nextjs-graphql-prisma-4-1k1kc83x3v/imgs/aws-s3-bucket-policy.png) Next, you need to allow your application, which will be on a different domain, to access the stored images. In the **Permissions** tab of your bucket, scroll to the **Cross-origin Resource Sharing (CORS)** section at the bottom and add the following to it: @@ -277,7 +269,6 @@ Next, you need to allow your application, which will be on a different domain, t } ] ``` - ![AWS S3](/fullstack-nextjs-graphql-prisma-4-1k1kc83x3v/imgs/aws-s3-cors.png) > Note: Before deploying your application, ensure you update the "AllowedOrigins" array with the URL pointing to your application. @@ -291,13 +282,12 @@ First, install the `aws-sdk` package by running the following command: ```shell npm install aws-sdk ``` - Next, create a new file called `upload-image.ts` located in the `pages/api/` directory and add the following code to it: ```ts // pages/api/upload-image.ts -import aws from "aws-sdk"; -import type { NextApiRequest, NextApiResponse } from "next"; +import aws from 'aws-sdk' +import type { NextApiRequest, NextApiResponse } from 'next' export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { @@ -306,15 +296,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) accessKeyId: process.env.APP_AWS_ACCESS_KEY, secretAccessKey: process.env.APP_AWS_SECRET_KEY, region: process.env.APP_AWS_REGION, - }); + }) // 2. aws.config.update({ accessKeyId: process.env.APP_AWS_ACCESS_KEY, secretAccessKey: process.env.APP_AWS_SECRET_KEY, region: process.env.APP_AWS_REGION, - signatureVersion: "v4", - }); + signatureVersion: 'v4', + }) // 3. const post = await s3.createPresignedPost({ @@ -324,18 +314,17 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) }, Expires: 60, // seconds Conditions: [ - ["content-length-range", 0, 5048576], // up to 1 MB + ['content-length-range', 0, 5048576], // up to 1 MB ], - }); + }) // 4. - return res.status(200).json(post); + return res.status(200).json(post) } catch (error) { - console.log(error); + console.log(error) } } ``` - 1. Creates a new instance of the S3 Bucket 1. Updates the main configuration class with the region, credentials, and additional request options 1. Generates a presigned URL allowing you to write to the S3 Bucket @@ -345,12 +334,12 @@ Finally, update the `pages/admin.tsx` file with the following code: ```tsx // pages/admin.tsx -import React from "react"; -import { type SubmitHandler, useForm } from "react-hook-form"; -import { gql, useMutation } from "@apollo/client"; -import toast, { Toaster } from "react-hot-toast"; -import type { GetServerSideProps } from "next"; -import { getSession } from "@auth0/nextjs-auth0"; +import React from 'react' +import { type SubmitHandler, useForm } from 'react-hook-form' +import { gql, useMutation } from '@apollo/client' +import toast, { Toaster } from 'react-hot-toast' +import type { GetServerSideProps } from 'next' +import { getSession } from '@auth0/nextjs-auth0' type FormValues = { title: string; @@ -358,23 +347,11 @@ type FormValues = { category: string; description: string; image: FileList; -}; +} const CreateLinkMutation = gql` - mutation ( - $title: String! - $url: String! - $imageUrl: String! - $category: String! - $description: String! - ) { - createLink( - title: $title - url: $url - imageUrl: $imageUrl - category: $category - description: $description - ) { + mutation($title: String!, $url: String!, $imageUrl: String!, $category: String!, $description: String!) { + createLink(title: $title, url: $url, imageUrl: $imageUrl, category: $category, description: $description) { title url imageUrl @@ -382,71 +359,68 @@ const CreateLinkMutation = gql` description } } -`; +` const Admin = () => { - const [createLink, { data, loading, error }] = useMutation(CreateLinkMutation); + const [createLink, { data, loading, error }] = useMutation(CreateLinkMutation) const { register, handleSubmit, formState: { errors }, - } = useForm(); + } = useForm() // Upload photo function const uploadPhoto = async (e: React.ChangeEvent) => { - if (!e.target.files || e.target.files.length <= 0) return; - const file = e.target.files[0]; - const filename = encodeURIComponent(file.name); - const res = await fetch(`/api/upload-image?file=${filename}`); - const data = await res.json(); - const formData = new FormData(); + if (!e.target.files || e.target.files.length <= 0) return + const file = e.target.files[0] + const filename = encodeURIComponent(file.name) + const res = await fetch(`/api/upload-image?file=${filename}`) + const data = await res.json() + const formData = new FormData() Object.entries({ ...data.fields, file }).forEach(([key, value]) => { // @ts-ignore - formData.append(key, value); - }); + formData.append(key, value) + }) toast.promise( fetch(data.url, { - method: "POST", + method: 'POST', body: formData, }), { - loading: "Uploading...", - success: "Image successfully uploaded!🎉", + loading: 'Uploading...', + success: 'Image successfully uploaded!🎉', error: `Upload failed 😥 Please try again ${error}`, }, - ); - }; + ) + } const onSubmit: SubmitHandler = async (data) => { - const { title, url, category, description, image } = data; - const imageUrl = `https://${process.env.NEXT_PUBLIC_AWS_S3_BUCKET_NAME}.s3.amazonaws.com/${image[0]?.name}`; - const variables = { title, url, category, description, imageUrl }; + const { title, url, category, description, image } = data + const imageUrl = `https://${process.env.NEXT_PUBLIC_AWS_S3_BUCKET_NAME}.s3.amazonaws.com/${image[0]?.name}` + const variables = { title, url, category, description, imageUrl } try { toast.promise(createLink({ variables }), { - loading: "Creating new link..", - success: "Link successfully created!🎉", + loading: 'Creating new link..', + success: 'Link successfully created!🎉', error: `Something went wrong 😥 Please try again - ${error}`, - }); + }) } catch (error) { - console.error(error); + console.error(error) } - }; + } return (

Create a new link

-
+
- ); -}; + ) +} -export default Admin; +export default Admin // getServerSideProps code remains unchanged ``` - The form includes a new input field to handle file upload. The input field accepts images of either `.png` or `.jpeg` formats. Whenever an image is uploaded, the `uploadPhoto` function sends a request to the `/api/upload-image` API endpoint. A toast will be shown as the request is being resolved by the API – success, loading, or error states. When the form is submitted, the URL of the image is included as a variable in the `createLink` mutation. A toast will appear as the mutation is being executed. @@ -531,3 +504,4 @@ When the form is submitted, the URL of the image is included as a variable in th ## Summary and next steps You learned how to add support for image upload using AWS S3. In the [next part](/fullstack-nextjs-graphql-prisma-5-m2fna60h7c), you will deploy your app to Vercel and learn how you can use the Prisma Data Proxy to manage your database connection pool to ensure your application doesn't run out of connections. + diff --git a/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-5-m2fna60h7c/index.mdx b/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-5-m2fna60h7c/index.mdx index ec90067d43..6c5984fb26 100644 --- a/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-5-m2fna60h7c/index.mdx +++ b/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-5-m2fna60h7c/index.mdx @@ -96,13 +96,11 @@ Before you deploy the application, you will make a few changes. Before you deploy your application, you will need to make a few updates to your application to make it work with the Prisma Data Proxy. First, update your `.env` file by renaming the existing `DATABASE_URL` to `MIGRATE_DATABASE_URL`. Create a `DATABASE_URL` variable and set the Prisma Data Proxy URL from the previous step here: - ``` # .env MIGRATE_DATABASE_URL="postgres://" DATABASE_URL="prisma://" ``` - The `MIGRATE_DATABASE_URL` will be used for making database schema changes to your database. ### Create new scripts in `package.json` @@ -115,7 +113,6 @@ Next, update your `package.json` file by adding a `vercel-build` script: "vercel-build": "npx prisma generate --data-proxy && next build", }, ``` - The `vercel-build` script will generate Prisma Client that uses the Prisma Data Proxy and build the application. ## Deploy the app to Vercel @@ -136,10 +133,10 @@ Refer to the `.env.example` file in the repository for the environment variables Once you've added the environment variables, click **Deploy**. + ![Configuring environment variables](/fullstack-nextjs-graphql-prisma-5-m2fna60h7c/imgs/vercel-environment-variables.png) Once your application is successfully deployed, copy its URL and: - - Update the **Allowed Callback URLs** and **Allowed Logout URLs** on the Auth0 Dashboard with the URL of your application - Update your Auth0 Action with the URL of the deployed application - Update the **AllowedOrigins** Cross-origin Resource Sharing (CORS) policy on S3 with the URL to your deployed application @@ -153,7 +150,6 @@ If everything works correctly, you will be able to view your deployed applicatio This article concludes the series. You learned how to build a full-stack app using modern tools that offer great developer experience and leveraged different services to get your application production-ready. You: - - Explored database modeling using Prisma - Built a GraphQL API using GraphQL Yoga and Pothos - Added authentication using Auth0 @@ -164,3 +160,4 @@ You: You can find the complete source code for the app on [GitHub](https://github.com/prisma/awesome-links). Feel free to raise issues or contribute to the repository if you find any bugs or want to make improvements. Feel free to reach out on [Twitter](https://twitter.com/thisismahmoud_) if you have any questions. + diff --git a/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-oklidw1rhw/index.mdx b/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-oklidw1rhw/index.mdx index 07ec4b4958..63eedb1fc1 100644 --- a/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-oklidw1rhw/index.mdx +++ b/apps/blog/content/blog/fullstack-nextjs-graphql-prisma-oklidw1rhw/index.mdx @@ -112,7 +112,6 @@ To get started, navigate into the directory of your choice and run the following ```shell git clone -b part-1 https://github.com/m-abdelwahab/awesome-links.git ``` - You can now navigate into the cloned directory, install the dependencies and start the development server: ```shell @@ -120,7 +119,6 @@ cd awesome-links npm install npm run dev ``` - Here's what the starter project looks like: ![Current state of the application](/fullstack-nextjs-graphql-prisma-oklidw1rhw/imgs/awesome-links-starter-project.png) @@ -152,7 +150,6 @@ awesome-links/ ┣ tailwind.config.js ┗ tsconfig.json ``` - This starter project is a Next.js app with TypeScript and TailwindCSS installed. @@ -166,18 +163,17 @@ The `_app.tsx` file is used to override the default `App` behavior. This file al ```tsx // pages/_app.tsx -import "../styles/tailwind.css"; // import Tailwind globally -import Layout from "../components/Layout"; // header layout persists between page changes +import '../styles/tailwind.css' // import Tailwind globally +import Layout from '../components/Layout' // header layout persists between page changes function MyApp({ Component, pageProps }) { return ( - ); + ) } -export default MyApp; +export default MyApp ``` - The data we see when navigating to `http://localhost:3000` is hardcoded in the [`/data/links.ts`](https://github.com/m-abdelwahab/awesome-links/blob/part-1/data/links.ts) file. In the upcoming parts, the data will be fetched dynamically from the database using a GraphQL API. ## Creating the data model for the app @@ -200,25 +196,21 @@ To get started, first install Prisma's CLI as a development dependency: ```shell npm install --save-dev prisma ``` - You can now use the Prisma CLI to create a basic Prisma setup by running: ```shell npx prisma init ``` - A new `/prisma` directory is created and inside it you will find a `schema.prisma` file. This is your main Prisma configuration file which will contain your database schema. A `.env` ([dotenv](https://github.com/motdotla/dotenv)) file is also added to the root of the project. This is where you define environment variables such as the database connection URL or access tokens. -Open the `.env` file and replace the dummy connection URL with the connection URL of your PostgreSQL database. - +Open the `.env` file and replace the dummy connection URL with the connection URL of your PostgreSQL database. ``` // .env # Example: postgresql://giwuzwpdnrgtzv:d003c6a604bb400ea955c3abd8c16cc98f2d909283c322ebd8e9164b33ccdb75@ec2-54-170-123-247.eu-west-1.compute.amazonaws.com:5432/d6ajekcigbuca9 DATABASE_URL="" ``` - The database URL you just added has the following structure: ![Database URL breakdown](/fullstack-nextjs-graphql-prisma-oklidw1rhw/imgs/database-url-breakdown.png) @@ -247,7 +239,6 @@ generator client { provider = "prisma-client-js" } ``` - > **Note**: This file uses PSL (Prisma Schema Language). To get the best possible development experience, make sure you install our [VSCode extension](https://marketplace.visualstudio.com/items?itemName=Prisma.prisma), which adds syntax highlighting, formatting, auto-completion, jump-to-definition, and linting for `.prisma` files. In the `datasource` field, we specified that we're using PostgreSQL and that we're loading the database URL from the `.env` file. @@ -277,7 +268,6 @@ enum Role { ADMIN } ``` - > **Note**: models are typically spelled in [PascalCase](https://wiki.c2.com/?pascalcase) and should use the singular form. (for example, `User` instead of `user`, `users` or `Users`) Here we defined a `User` model with several fields. Each field has a name followed by a type and [optional field attributes](https://www.prisma.io/docs/orm/prisma-schema/data-model/models#defining-fields). @@ -302,7 +292,6 @@ model Link { category String } ``` - ```prisma @@ -310,31 +299,30 @@ model Link { // code above unchanged model User { -id Int @id @default(autoincrement()) -createdAt DateTime @default(now()) -updatedAt DateTime @updatedAt -email String? @unique -image String? -role Role @default(USER) + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + email String? @unique + image String? + role Role @default(USER) } enum Role { -USER -ADMIN + USER + ADMIN } model Link { -id Int @id @default(autoincrement()) -createdAt DateTime @default(now()) -updatedAt DateTime @updatedAt -title String -description String -url String -imageUrl String -category String + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + title String + description String + url String + imageUrl String + category String } - -```` +``` @@ -374,14 +362,13 @@ model Link { category String + users User[] } -```` - +``` This is an [_implicit_ many-to-many](https://www.prisma.io/docs/orm/prisma-schema/data-model/relations#implicit-many-to-many-relations) relation, where we have a relation table in the underlying database. This [relation table](https://www.prisma.io/docs/orm/prisma-schema/data-model/relations#relation-tables) is managed by Prisma. Here's what the final schema looks like: ```prisma -copy +copy // prisma/schema.prisma datasource db { @@ -420,7 +407,6 @@ model Link { users User[] } ``` - ## Migrating and pushing changes to the database To create these tables in the database, you will use the `prisma migrate dev` command: @@ -428,9 +414,7 @@ To create these tables in the database, you will use the `prisma migrate dev` co ```shell npx prisma migrate dev --name init ``` - The command does the following things: - - Generate a new SQL migration called `init` - Apply the migration to the database - Install Prisma Client if it's not yet installed @@ -441,7 +425,6 @@ If Prisma Client is not automatically installed, you can install it with the fol ```shell npm install @prisma/client ``` - Inside the `prisma` directory, you will notice a new folder called `migrations`. It should also contain another folder that ends with `init` and contains a file called `migration.sql`. The `migration.sql` file contains the generated SQL. @@ -452,54 +435,51 @@ CREATE TYPE "Role" AS ENUM ('USER', 'ADMIN'); -- CreateTable CREATE TABLE "User" ( -"id" SERIAL NOT NULL, -"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, -"updatedAt" TIMESTAMP(3) NOT NULL, -"email" TEXT, -"image" TEXT, -"role" "Role" NOT NULL DEFAULT 'USER', + "id" SERIAL NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "email" TEXT, + "image" TEXT, + "role" "Role" NOT NULL DEFAULT 'USER', CONSTRAINT "User_pkey" PRIMARY KEY ("id") - ); -- CreateTable CREATE TABLE "Link" ( -"id" SERIAL NOT NULL, -"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, -"updatedAt" TIMESTAMP(3) NOT NULL, -"title" TEXT NOT NULL, -"description" TEXT NOT NULL, -"url" TEXT NOT NULL, -"imageUrl" TEXT NOT NULL, -"category" TEXT NOT NULL, + "id" SERIAL NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "title" TEXT NOT NULL, + "description" TEXT NOT NULL, + "url" TEXT NOT NULL, + "imageUrl" TEXT NOT NULL, + "category" TEXT NOT NULL, CONSTRAINT "Link_pkey" PRIMARY KEY ("id") - ); -- CreateTable -CREATE TABLE "\_LinkToUser" ( -"A" INTEGER NOT NULL, -"B" INTEGER NOT NULL +CREATE TABLE "_LinkToUser" ( + "A" INTEGER NOT NULL, + "B" INTEGER NOT NULL ); -- CreateIndex CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); -- CreateIndex -CREATE UNIQUE INDEX "\_LinkToUser_AB_unique" ON "\_LinkToUser"("A", "B"); +CREATE UNIQUE INDEX "_LinkToUser_AB_unique" ON "_LinkToUser"("A", "B"); -- CreateIndex -CREATE INDEX "\_LinkToUser_B_index" ON "\_LinkToUser"("B"); +CREATE INDEX "_LinkToUser_B_index" ON "_LinkToUser"("B"); -- AddForeignKey -ALTER TABLE "\_LinkToUser" ADD CONSTRAINT "\_LinkToUser_A_fkey" FOREIGN KEY ("A") REFERENCES "Link"("id") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "_LinkToUser" ADD CONSTRAINT "_LinkToUser_A_fkey" FOREIGN KEY ("A") REFERENCES "Link"("id") ON DELETE CASCADE ON UPDATE CASCADE; -- AddForeignKey -ALTER TABLE "\_LinkToUser" ADD CONSTRAINT "\_LinkToUser_B_fkey" FOREIGN KEY ("B") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; - -```` +ALTER TABLE "_LinkToUser" ADD CONSTRAINT "_LinkToUser_B_fkey" FOREIGN KEY ("B") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; +``` @@ -537,8 +517,7 @@ main() .finally(async () => { await prisma.$disconnect() }) -```` - +``` We are first creating a user using the [`create()`](https://www.prisma.io/docs/orm/reference/prisma-client-reference#create) function, which creates a new database record. Next, we are using the [`createMany()`](https://www.prisma.io/docs/orm/reference/prisma-client-reference#createmany) function to create multiple records. We are passing the hard-coded data we have as a parameter. @@ -548,7 +527,6 @@ By default, Next.js [forces the use of `ESNext` modules](https://github.com/verc ```shell npm install --save-dev ts-node ``` - Then in the `tsconfig.json` file, specify that `ts-node` will use `CommonJS` modules: ```json @@ -571,10 +549,9 @@ diff + } } ``` - Update your `package.json` file by adding a `prisma` key with a `seed` property defining the script for seeding the database: -````json +```json diff { // ... "devDependencies": { @@ -598,8 +575,7 @@ You can now seed your database by running the following command: ```shell npx prisma db seed -```` - +``` If everything worked correctly you should see the following output: ```shell @@ -609,7 +585,6 @@ Running seed: ts-node --compiler-options '{"module":"CommonJS"}' "prisma/seed.ts 🌱 Your database has been seeded. ``` - ## Use Prisma Studio to explore your database @@ -621,7 +596,6 @@ To start Prisma Studio, run the following command ```shell npx prisma studio ``` - If you've done all the steps correctly you should you have the `Link` and `User` models inside your database. Inside the `Link` model you'll find 4 records and for the `User` model you'll find 1 record. ![Prisma Studio](/fullstack-nextjs-graphql-prisma-oklidw1rhw/imgs/awesome-links-prisma-studio.png) @@ -636,3 +610,4 @@ In [the next part](/fullstack-nextjs-graphql-prisma-2-fwpc6ds155) of the course, - Building a GraphQL API for our app using GraphQL Yoga and Pothos - Consuming the API on the client using Apollo Client. - GraphQL pagination so that we don't load all links at once and have better performance. + diff --git a/apps/blog/content/blog/fullstack-remix-prisma-mongodb-1-7d0bftxbmb6r/index.mdx b/apps/blog/content/blog/fullstack-remix-prisma-mongodb-1-7d0bftxbmb6r/index.mdx index 103871aabf..cee459f00b 100644 --- a/apps/blog/content/blog/fullstack-remix-prisma-mongodb-1-7d0bftxbmb6r/index.mdx +++ b/apps/blog/content/blog/fullstack-remix-prisma-mongodb-1-7d0bftxbmb6r/index.mdx @@ -109,7 +109,6 @@ To start off a Remix project, run the following command in a location where you ```shell npx create-remix@latest kudos ``` - This will scaffold a starter project for you and ask you a couple of questions. Choose the following options to let Remix know you want a blank project using TypeScript and you intend to deploy it to Vercel. - What type of app do you want to create? **Just the basics** @@ -122,7 +121,6 @@ This will scaffold a starter project for you and ask you a couple of questions. Once the project is set up, go ahead and pop it open by either opening the project in your code editor or by running the command `code .` within that folder in your terminal if you are using [VSCode's CLI](https://code.visualstudio.com/docs/editor/command-line). You will see the generated boilerplate project with a file structure that looks like this: - ``` │── app │ ├── entry.client.tsx @@ -143,7 +141,6 @@ You will see the generated boilerplate project with a file structure that looks ├── README.md └── vercel.json ``` - For the majority of this series, you will be working within the `app` directory, which will hold all of the custom code for this application. Any file within `./app/routes` will be turned into a route. For example, assuming your application is running on `localhost:3000`, the `./app/routes/index.tsx` file will result in a generated route at `localhost:3000/`. If you were to create another file at `app/routes/home.tsx`, Remix would generate a `localhost:3000/home` route in your site. @@ -169,7 +166,6 @@ To start things off, there are a few dependencies you will need in order to use ```shell npm install -D tailwindcss postcss autoprefixer concurrently ``` - This will install the following development dependencies: - [`tailwindcss`](https://tailwindcss.com/): The command-line interface _(CLI)_ that allows you to initialize a Tailwind configuration. @@ -182,7 +178,6 @@ Once those are installed, you can initialize Tailwind in the project: ```shell npx tailwindcss init -p ``` - This will generate two files: - `tailwind.config.js`: This is where you can tweak and extend TailwindCSS. See all of the options [here](https://tailwindcss.com/docs/configuration). @@ -191,17 +186,18 @@ This will generate two files: When a build is run, Tailwind will scan through the codebase to determine which of its utility classes it needs to bundle into its generated output. You will need to let Tailwind know which files it should look at to determine this. In `tailwind.config.js`, add the following glob pattern to the `content` key: ```js -diff; +diff // tailwind.config.js module.exports = { - content: [+"./app/**/*.{js,ts,jsx,tsx}"], + content: [ ++ "./app/**/*.{js,ts,jsx,tsx}", + ], theme: { extend: {}, }, plugins: [], -}; +} ``` - This will tell Tailwind that any file inside of the `app` folder with the provided extensions should be scanned through for keywords and class names that Tailwind will pick up on to generate its output file. Next, in `package.json` update your `scripts` section to include a build process for Tailwind when the application is built and when the development server runs. Add the following scripts: @@ -217,7 +213,6 @@ Next, in `package.json` update your `scripts` section to include a build process } } ``` - You may notice a few of the scripts are pointing to a file at `./styles/app.css` that does not exist yet. This will be Tailwind's source file when it is built and where you will import the various [functions and directives](https://tailwindcss.com/docs/functions-and-directives) Tailwind will use. Go ahead and create that source file at `./styles/app.css` and add each of Tailwind's [layers](https://tailwindcss.com/docs/adding-custom-styles#using-css-and-layer) using the [`@tailwind`](https://tailwindcss.com/docs/functions-and-directives#tailwind) directive: @@ -228,7 +223,6 @@ Go ahead and create that source file at `./styles/app.css` and add each of Tailw @tailwind components; @tailwind utilities; ``` - Now when the application is run or built, your `scripts` will also kick off the process to run Tailwind's scanning and building process. The result of this will be outputted into `app/styles/app.css`. That file is what you will import into your Remix application to allow you to use Tailwind in your code! @@ -241,18 +235,17 @@ In `app/root.tsx`, import the generated stylesheet and export a [`links`](https: import type { MetaFunction, LinksFunction } from "@remix-run/node"; // 2 -import styles from "./styles/app.css"; +import styles from './styles/app.css'; // ... // 3 export const links: LinksFunction = () => { - return [{ rel: "stylesheet", href: styles }]; -}; + return [{ rel: 'stylesheet', href: styles }] +} // ... ``` - The code above will: 1. Import the type for Remix's `links` function. @@ -270,10 +263,9 @@ export default function Index() {

TailwindCSS Is Working!

- ); + ) } ``` - You should see a screen that looks something like this: ![](/fullstack-remix-prisma-mongodb-1-7d0bftxbmb6r/imgs/tailwind-css-checkpoint.png) @@ -296,14 +288,13 @@ Head over to the Atlas home page linked above. If you don't already have an acco If you will be using an existing account, head to the dashboard. From there you will see a dropdown in the top left corner of the screen. If you pop that open you will see the option New Project. -![](/fullstack-remix-prisma-mongodb-1-7d0bftxbmb6r/imgs/new-project.png) - -Once you click on that, hit the **Build a Database** button. + ![](/fullstack-remix-prisma-mongodb-1-7d0bftxbmb6r/imgs/new-project.png) -![](/fullstack-remix-prisma-mongodb-1-7d0bftxbmb6r/imgs/build-a-database.png) + Once you click on that, hit the **Build a Database** button. -From there you should be able to follow along with the rest of the steps below. + ![](/fullstack-remix-prisma-mongodb-1-7d0bftxbmb6r/imgs/build-a-database.png) + From there you should be able to follow along with the rest of the steps below. @@ -325,7 +316,7 @@ Then, in the **Where would you like to connect from?** section, hit **Add My Cur ![](/fullstack-remix-prisma-mongodb-1-7d0bftxbmb6r/imgs/mongodb-ip-setup.png) -With those steps completed, your database should finish its provisioning process within a few minutes _(at most)_ and be ready for you to play with! +With those steps completed, your database should finish its provisioning process within a few minutes *(at most)* and be ready for you to play with! ## Set up Prisma @@ -338,13 +329,11 @@ The first thing you will want to do is install the [Prisma CLI](https://www.pris ```shell npm i -D prisma ``` - To initialize Prisma within the project, simply run: ```shell npx prisma init --datasource-provider mongodb ``` - This will create a few different files in your project. You will see a `prisma` folder with a `schema.prisma` file inside of it. This is where you will define your schema and model out your data. It will also generate a `.env` file automatically if one did not previously exist with a sample environment variable that will hold your database's connection string. @@ -365,7 +354,6 @@ datasource db { url = env("DATABASE_URL") } ``` - > **Note**: This file is written in PSL (Prisma Schema Language), which allows you to map out your schema. For more information on Prisma schemas and PSL, check out the [Prisma docs](https://www.prisma.io/docs/orm/prisma-schema). In the `url` of the [`datasource`](https://www.prisma.io/docs/orm/prisma-schema/overview/data-sources) block, you can see it references the `DATABASE_URL` environment variable from the `.env` file using the `env()` function PSL provides. Prisma uses [dotenv](https://www.npmjs.com/package/dotenv) under the hood to expose those variables to Prisma. @@ -391,13 +379,11 @@ In your `.env` file, replace the default connection string with your MongoDB con ```shell mongodb+srv://USERNAME:PASSWORD@HOST:PORT/DATABASE ``` - After pasting in your connection string and modifying it to match the above format, you should be left with a string that looks like this: ```shell mongodb+srv://sadams:@cluster0.vv1we.mongodb.net/kudos?retryWrites=true&w=majority ``` - > **Note**: Notice the `kudos` database name. You can put any name you want for your `DATABASE` here. MongoDB will automatically create the new database if it does not already exist. > > For more details on connecting to your MongoDB database, check out the [docs](https://www.prisma.io/docs/getting-started/setup-prisma/add-to-existing-project/mongodb/connect-your-database-typescript-mongodb). @@ -416,7 +402,6 @@ model User { } ``` - > **Note**: MongoDB is a schemaless database built for flexible data so it may seem counterintuitive to define a "schema" for the data you are storing in it. As schemaless databases grow and evolve, however, the problem occurs where it becomes difficult to keep track of what data lives where while accounting for legacy data shapes. Because of this, defining a schema may save some headaches in the long run. Every Prisma model needs to have a unique `id` field. @@ -427,7 +412,6 @@ model User { id String @id @default(auto()) @map("_id") @db.ObjectId } ``` - The code above will create an `id` field and let Prisma know this is a unique identifier with the [`@id`](https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#id) attribute. Because MongoDB automatically creates an `_id` field for every collection, you will let Prisma know using the [`@map`](https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#map) attribute that while you are calling this field `id` in the schema, it should map to the `_id` field in the database. The code will also define the data type for your `id` field and set a default value of `auto()`, which will allow you to make use of MongoDB's automatically generated unique IDs. @@ -446,7 +430,6 @@ model User { password String } ``` - As you can see above, you will be adding two `DateTime` type fields that will keep track of when a user gets created and when it is updated. The [`@updatedAt`](https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#updatedat) attribute will automatically update that field with a current timestamp any time that user is updated. It will also add an `email` field of type `String` that must be unique, indicated by the [`@unique`](https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#unique) attribute. This means no other user can have the same email. @@ -462,16 +445,13 @@ After making changes to our schema you can run the command: ```shell npx prisma db push ``` - This will push your schema changes to MongoDB, creating any new collections or indexes you have defined. For example, when you push your schema as it is now, you should see the following in the output: - ``` Applying the following changes: [+] Collection `User` [+] Unique index `User_email_key` on ({"email":1}) ``` - Because MongoDB is _schemaless_, there is no real concept of _migrations_. A schemaless database's data can fluidly change and evolve as the application's scope grows and changes. This command simply creates the defined collections and indexes. ![](/fullstack-remix-prisma-mongodb-1-7d0bftxbmb6r/imgs/user-collection.png) @@ -486,3 +466,4 @@ In the next article you will learn about: - Storing and modifying user data with Prisma and MongoDB - Building a Login form - Building a Signup form + diff --git a/apps/blog/content/blog/fullstack-remix-prisma-mongodb-2-ZTmOy58p4re8/index.mdx b/apps/blog/content/blog/fullstack-remix-prisma-mongodb-2-ZTmOy58p4re8/index.mdx index bb97ecf5b6..dfa1d1ad6b 100644 --- a/apps/blog/content/blog/fullstack-remix-prisma-mongodb-2-ZTmOy58p4re8/index.mdx +++ b/apps/blog/content/blog/fullstack-remix-prisma-mongodb-2-ZTmOy58p4re8/index.mdx @@ -79,10 +79,9 @@ export default function Login() {

Login Route

- ); + ) } ``` - The default export of a route file is the component Remix renders into the browser. Start the development server using `npm run dev` and navigate to [`http://localhost:3000/login`](http://localhost:3000/login), and you should see the route rendered. @@ -99,18 +98,18 @@ First, create a component you will wrap your routes in to provide some shared fo ### Composition -**Composition** is a pattern where you provide a component a set of child elements via its `props`. The `children` prop represents the elements defined between the opening and closing tag of the parent component. For example, consider this usage of a component named `Parent`: - -```tsx -

The child

-
-``` + **Composition** is a pattern where you provide a component a set of child elements via its `props`. The `children` prop represents the elements defined between the opening and closing tag of the parent component. For example, consider this usage of a component named `Parent`: -In this case, the `

` tag is a child of the `Parent` component and will be rendered into the `Parent` component wherever you decide to render the `children` prop value. + ```tsx +

The child

+ + ``` + In this case, the `

` tag is a child of the `Parent` component and will be rendered into the `Parent` component wherever you decide to render the `children` prop value. + To see this in action, create a new folder inside the `app` folder named `components`. Inside of that folder create a new file named `layout.tsx`. In that file, export the following [function component](https://reactjs.org/docs/components-and-props.html): @@ -118,50 +117,50 @@ In that file, export the following [function component](https://reactjs.org/docs ```tsx // app/components/layout.tsx export function Layout({ children }: { children: React.ReactNode }) { - return

{children}
; + return
{children}
} ``` - This component uses Tailwind classes to specify you want anything wrapped in the component to take up the full width and height of the screen, use the mono font, and show a moderately dark blue as the background. Notice the `children` prop is rendered inside the `
`. To see how this will get rendered when put to use, check out the snippets below: + + ```tsx

Child Element

``` - ```tsx

Child Element

``` + ## Create the sign in form Now you can import that component into the `app/routes/login.tsx` file and wrap your `

` tag inside of the new `Layout` component instead of the `
` where it currently lives: ```tsx // app/routes/login.tsx -import { Layout } from "~/components/layout"; +import { Layout } from '~/components/layout' export default function Login() { return (

Login Route

- ); + ) } ``` - ### Build the form Next add a sign in form that takes in `email` and `password` inputs and displays a submit button. Add a nice welcome message at the top to greet users when they enter your site and center the entire form on the screen using [Tailwind's flex classes](https://tailwindcss.com/docs/flex). ```tsx // app/routes/login.tsx -import { Layout } from "~/components/layout"; +import { Layout } from '~/components/layout' export default function Login() { return ( @@ -179,12 +178,7 @@ export default function Login() { - +
- ); + ) } ``` - ![](/fullstack-remix-prisma-mongodb-2-ZTmOy58p4re8/imgs/login-form.png) At this point, you don't need to worry about where the `
`'s action is pointing, just that it has a `method` value of `"post"`. Later on you will check out some cool Remix magic that sets up the action for us! @@ -213,20 +206,14 @@ Create a new file in `app/components` named `form-field.tsx` where you will buil ```tsx // app/components/form-field.tsx interface FormFieldProps { - htmlFor: string; - label: string; - type?: string; - value: any; - onChange?: (...args: any) => any; + htmlFor: string + label: string + type?: string + value: any + onChange?: (...args: any) => any } -export function FormField({ - htmlFor, - label, - type = "text", - value, - onChange = () => {}, -}: FormFieldProps) { +export function FormField({ htmlFor, label, type = 'text', value, onChange = () => {} }: FormFieldProps) { return ( <>
- ); + ) } ``` - Two changes were made here: 1. You added two new keys to the `formData` state. @@ -468,7 +446,6 @@ Before moving on, however, you will need a new dependency in your project. Run t ```shell npm i bcryptjs && npm i -D @types/bcryptjs ``` - This installs the [`bcryptjs`](https://www.npmjs.com/package/bcryptjs) library and its type definitions. You will use this later on to hash and compare passwords. Authentication will be session-based, following the same patterns used in the [authentication](https://remix.run/docs/en/v1/tutorials/jokes#authentication) for Remix's [Jokes App](https://remix.run/docs/en/v1/tutorials/jokes) tutorial. @@ -502,27 +479,24 @@ Export an async function from `app/utils/auth.server.ts` named `register`: // app/utils/auth.server.ts export async function register() {} ``` - Create and export a `type` defining the fields the register form will provide in another new file within `app/utils` named `types.server.ts`. ```tsx // app/utils/types.server.ts export type RegisterForm = { - email: string; - password: string; - firstName: string; - lastName: string; -}; + email: string + password: string + firstName: string + lastName: string +} ``` - Import that `type` into `app/utils/auth.server.ts` and use it in the `register` function to describe a `user` parameter, which will contain the sign up form's data: ```tsx // app/utils/auth.server.ts -import type { RegisterForm } from "./types.server"; +import type { RegisterForm } from './types.server' export async function register(user: RegisterForm) {} ``` - When this `register` function is called and supplied a `user`, the first thing you will need to check is whether or not a user already exists with the email provided. > **Note**: Remember, the `email` field is defined as unique in your schema. @@ -535,27 +509,26 @@ Create a new file in the `app/utils` folder named `prisma.server.ts` where you w ```tsx // app/utils/prisma.server.ts -import { PrismaClient } from "@prisma/client"; +import { PrismaClient } from '@prisma/client' -let prisma: PrismaClient; +let prisma: PrismaClient declare global { - var __db: PrismaClient | undefined; + var __db: PrismaClient | undefined } -if (process.env.NODE_ENV === "production") { - prisma = new PrismaClient(); - prisma.$connect(); +if (process.env.NODE_ENV === 'production') { + prisma = new PrismaClient() + prisma.$connect() } else { if (!global.__db) { - global.__db = new PrismaClient(); - global.__db.$connect(); + global.__db = new PrismaClient() + global.__db.$connect() } - prisma = global.__db; + prisma = global.__db } -export { prisma }; +export { prisma } ``` - > **Note**: There are precautions put into place above that prevent live-reloads from saturating your database with connections while developing. You now have a way to access your database. In `app/utils/auth.server.ts`, import the instantiated `PrismaClient` and add the following to the `register` function: @@ -563,18 +536,17 @@ You now have a way to access your database. In `app/utils/auth.server.ts`, impor ```tsx // app/utils/auth.server.ts -import type { RegisterForm } from "./types.server"; -import { prisma } from "./prisma.server"; -import { json } from "@remix-run/node"; +import type { RegisterForm } from './types.server' +import { prisma } from './prisma.server' +import { json } from '@remix-run/node' export async function register(user: RegisterForm) { - const exists = await prisma.user.count({ where: { email: user.email } }); + const exists = await prisma.user.count({ where: { email: user.email } }) if (exists) { - return json({ error: `User already exists with that email` }, { status: 400 }); + return json({ error: `User already exists with that email` }, { status: 400 }) } } ``` - The register funciton will now query for any user in your database with the email provided. The `count` function was used here becuase it returns a numeric value. If there are no records matching the query, it will return `0` which evaluates to `false`. Otherwise, a value greater than `0` will be returned which evaluates to `true`. @@ -599,7 +571,6 @@ type Profile { lastName String } ``` - The `type` keyword is used to define a composite type – allowing you to define a document inside the document. The benefit of using a composite type over a JSON type is that you get type-safety when querying documents. This is _super_ helpful as it gives you the capability of explicitly defining the shape of data that would otherwise have been fluid and capable of containing anything due to MongoDB's flexible nature. @@ -622,13 +593,11 @@ model User { // ... ``` - Awesome, your `User` model will now contain a `profile` embedded document. Re-generate Prisma Client to account for these new changes: ```shell npx prisma generate ``` - > **Note**: You do not need to run `prisma db push` because you have not added any new collections or indexes. ### Add a user service @@ -637,12 +606,12 @@ Create another file in `app/utils` named `user.server.ts` where any user-specifi ```tsx // app/utils/user.server.ts -import bcrypt from "bcryptjs"; -import type { RegisterForm } from "./types.server"; -import { prisma } from "./prisma.server"; +import bcrypt from 'bcryptjs' +import type { RegisterForm } from './types.server' +import { prisma } from './prisma.server' export const createUser = async (user: RegisterForm) => { - const passwordHash = await bcrypt.hash(user.password, 10); + const passwordHash = await bcrypt.hash(user.password, 10) const newUser = await prisma.user.create({ data: { email: user.email, @@ -652,11 +621,10 @@ export const createUser = async (user: RegisterForm) => { lastName: user.lastName, }, }, - }); - return { id: newUser.id, email: user.email }; -}; + }) + return { id: newUser.id, email: user.email } +} ``` - This `createUser` function does a couple of things: 1. It hashes the password provided in the registration form because you should not store it as plain-text. @@ -697,7 +665,6 @@ export async function register(user: RegisterForm) { + } } ``` - Now, when a user registers, if another user does not already exist with the provided email a new one will be created. If something goes wrong during the creation of the user, an error will be returned to the client along with the values that were passed in for the `email` and `password`. ## Build the login function @@ -710,19 +677,18 @@ The `login` function will take in an `email` and a `password`, so to start this // ... export type LoginForm = { - email: string; - password: string; -}; + email: string + password: string +} ``` - Then create the `login` function by adding the following to `app/utils/auth.server.ts`: ```ts // app/utils/auth.server.ts // 1 -import { RegisterForm, LoginForm } from "./types.server"; -import bcrypt from "bcryptjs"; +import { RegisterForm, LoginForm } from './types.server' +import bcrypt from 'bcryptjs' //... @@ -730,17 +696,16 @@ export async function login({ email, password }: LoginForm) { // 2 const user = await prisma.user.findUnique({ where: { email }, - }); + }) // 3 if (!user || !(await bcrypt.compare(password, user.password))) - return json({ error: `Incorrect login` }, { status: 400 }); + return json({ error: `Incorrect login` }, { status: 400 }) // 4 - return { id: user.id, email }; + return { id: user.id, email } } ``` - The code above ... 1. ... imports the new `type` and the `bcryptjs` library. @@ -760,30 +725,29 @@ Import that function into `app/utils/auth.server.ts` and add a new cookie sessio // app/utils/auth.server.ts // Added the createCookieSessionStorage function 👇 -import { json, createCookieSessionStorage } from "@remix-run/node"; +import { json, createCookieSessionStorage } from '@remix-run/node' // ... -const sessionSecret = process.env.SESSION_SECRET; +const sessionSecret = process.env.SESSION_SECRET if (!sessionSecret) { - throw new Error("SESSION_SECRET must be set"); + throw new Error('SESSION_SECRET must be set') } const storage = createCookieSessionStorage({ cookie: { - name: "kudos-session", - secure: process.env.NODE_ENV === "production", + name: 'kudos-session', + secure: process.env.NODE_ENV === 'production', secrets: [sessionSecret], - sameSite: "lax", - path: "/", + sameSite: 'lax', + path: '/', maxAge: 60 * 60 * 24 * 30, httpOnly: true, }, -}); +}) // login & register functions... ``` - The code above creates a session storage with a couple of settings: - `name`: The name of the cookie. @@ -802,28 +766,26 @@ You will also need to set up a session secret in the `.env` file. Add a variable // .env SESSION_SECRET="supersecretvalue" ``` - The session storage is now set up. Create one more function in `app/utils/auth.server.ts` that will actually create the cookie session: ```ts // app/utils/auth.server.ts // 👇 Added the redirect function -import { redirect, json, createCookieSessionStorage } from "@remix-run/node"; +import { redirect, json, createCookieSessionStorage } from '@remix-run/node' // ... export async function createUserSession(userId: string, redirectTo: string) { - const session = await storage.getSession(); - session.set("userId", userId); + const session = await storage.getSession() + session.set('userId', userId) return redirect(redirectTo, { headers: { - "Set-Cookie": await storage.commitSession(session), + 'Set-Cookie': await storage.commitSession(session), }, - }); + }) } ``` - This function ... - ... creates a new session. @@ -872,7 +834,6 @@ export async function login({ email, password }: LoginForm) { + return createUserSession(user.id, "/"); } ``` - ### Handle the login and register form submissions You have created all of the functions needed to create new users and log them in. Now you will put those to use in the forms you built. @@ -884,12 +845,11 @@ In `app/routes/login.tsx`, export an [`action`](https://remix.run/docs/en/v1/tut // ... -import { ActionFunction } from "@remix-run/node"; -export const action: ActionFunction = async ({ request }) => {}; +import { ActionFunction } from '@remix-run/node' +export const action: ActionFunction = async ({ request }) => {} // ... ``` - > **Note**: Remix looks for an exported function named `action` to set up a POST request on the route you are defining. Now create a couple of validator functions in a new file inside of `app/utils` named `validators.server.ts` that will be used to validate the form input. @@ -900,67 +860,62 @@ Now create a couple of validator functions in a new file inside of `app/utils` n export const validateEmail = (email: string): string | undefined => { var validRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/; if (!email.length || !validRegex.test(email)) { - return "Please enter a valid email address"; + return "Please enter a valid email address" } -}; +} export const validatePassword = (password: string): string | undefined => { if (password.length < 5) { - return "Please enter a password that is at least 5 characters long"; + return "Please enter a password that is at least 5 characters long" } -}; +} export const validateName = (name: string): string | undefined => { - if (!name.length) return `Please enter a value`; -}; + if (!name.length) return `Please enter a value` +} ``` - Within the `action` function in `app/routes/login.tsx`, grab the form data from the request and validate it is of the correct format. ```tsx // app/routes/login.tsx // ... // Added the json function 👇 -import { ActionFunction, json } from "@remix-run/node"; -import { validateEmail, validateName, validatePassword } from "~/utils/validators.server"; +import { ActionFunction, json } from '@remix-run/node' +import { validateEmail, validateName, validatePassword } from '~/utils/validators.server' export const action: ActionFunction = async ({ request }) => { - const form = await request.formData(); - const action = form.get("_action"); - const email = form.get("email"); - const password = form.get("password"); - let firstName = form.get("firstName"); - let lastName = form.get("lastName"); - - if (typeof action !== "string" || typeof email !== "string" || typeof password !== "string") { - return json({ error: `Invalid Form Data`, form: action }, { status: 400 }); + const form = await request.formData() + const action = form.get('_action') + const email = form.get('email') + const password = form.get('password') + let firstName = form.get('firstName') + let lastName = form.get('lastName') + + if (typeof action !== 'string' || typeof email !== 'string' || typeof password !== 'string') { + return json({ error: `Invalid Form Data`, form: action }, { status: 400 }) } - if (action === "register" && (typeof firstName !== "string" || typeof lastName !== "string")) { - return json({ error: `Invalid Form Data`, form: action }, { status: 400 }); + if (action === 'register' && (typeof firstName !== 'string' || typeof lastName !== 'string')) { + return json({ error: `Invalid Form Data`, form: action }, { status: 400 }) } const errors = { email: validateEmail(email), password: validatePassword(password), - ...(action === "register" + ...(action === 'register' ? { - firstName: validateName((firstName as string) || ""), - lastName: validateName((lastName as string) || ""), + firstName: validateName((firstName as string) || ''), + lastName: validateName((lastName as string) || ''), } : {}), - }; + } if (Object.values(errors).some(Boolean)) - return json( - { errors, fields: { email, password, firstName, lastName }, form: action }, - { status: 400 }, - ); -}; + return json({ errors, fields: { email, password, firstName, lastName }, form: action }, { status: 400 }) +} // ... ``` - The code above may look a bit scary, but in a nutshell it ... - ... pulls the form data out of the request object. @@ -993,7 +948,6 @@ export const action: ActionFunction = async ({ request }) => { // ... ``` - The `switch` statement will allow you to conditionally run the `login` and `register` functions depending on what the `_action` value from the form contains. In order to actually trigger this action, the forms need to post to this route. Fortunately, Remix will take care of this, as it automatically configures `POST` requests to the `/login` route when it recognizes the exported `action` function. @@ -1012,57 +966,53 @@ In `app/utils/auth.server.ts` you will need to add a few helper functions. // app/utils/auth.server.ts // ... -export async function requireUserId( - request: Request, - redirectTo: string = new URL(request.url).pathname, -) { - const session = await getUserSession(request); - const userId = session.get("userId"); - if (!userId || typeof userId !== "string") { - const searchParams = new URLSearchParams([["redirectTo", redirectTo]]); - throw redirect(`/login?${searchParams}`); +export async function requireUserId(request: Request, redirectTo: string = new URL(request.url).pathname) { + const session = await getUserSession(request) + const userId = session.get('userId') + if (!userId || typeof userId !== 'string') { + const searchParams = new URLSearchParams([['redirectTo', redirectTo]]) + throw redirect(`/login?${searchParams}`) } - return userId; + return userId } function getUserSession(request: Request) { - return storage.getSession(request.headers.get("Cookie")); + return storage.getSession(request.headers.get('Cookie')) } async function getUserId(request: Request) { - const session = await getUserSession(request); - const userId = session.get("userId"); - if (!userId || typeof userId !== "string") return null; - return userId; + const session = await getUserSession(request) + const userId = session.get('userId') + if (!userId || typeof userId !== 'string') return null + return userId } export async function getUser(request: Request) { - const userId = await getUserId(request); - if (typeof userId !== "string") { - return null; + const userId = await getUserId(request) + if (typeof userId !== 'string') { + return null } try { const user = await prisma.user.findUnique({ where: { id: userId }, select: { id: true, email: true, profile: true }, - }); - return user; + }) + return user } catch { - throw logout(request); + throw logout(request) } } export async function logout(request: Request) { - const session = await getUserSession(request); - return redirect("/login", { + const session = await getUserSession(request) + return redirect('/login', { headers: { - "Set-Cookie": await storage.destroySession(session), + 'Set-Cookie': await storage.destroySession(session), }, - }); + }) } ``` - This is a lot of new functionality. Here is what the functions above will do: - `requireUserId` checks for a user's session. If one exists, it is a success and just returns the `userId`. If it fails, however, it will redirect the user to the login screen. @@ -1078,17 +1028,16 @@ In `app/routes/index.tsx`, return the user to the login screen if they are not l ```tsx // app/routes/index.tsx -import { LoaderFunction } from "@remix-run/node"; -import { requireUserId } from "~/utils/auth.server"; +import { LoaderFunction } from '@remix-run/node' +import { requireUserId } from '~/utils/auth.server' export const loader: LoaderFunction = async ({ request }) => { - await requireUserId(request); - return null; -}; + await requireUserId(request) + return null +} // ... ``` - > **Note**: Remix runs the [`loader`](https://remix.run/docs/en/v1/api/conventions#loader) function **before** serving your page. This means any redirects in a loader will trigger before your page can be served. If you try to navigate to the base route _(`/`)_ of your application while not logged in you should be redirected to the login screen with a `redirectTo` param in the URL. @@ -1100,16 +1049,15 @@ Next, do essentially the opposite. If a logged in user tries to get to the login ```tsx // app/routes/login.tsx // ... -import { ActionFunction, json, LoaderFunction, redirect } from "@remix-run/node"; -import { login, register, getUser } from "~/utils/auth.server"; +import { ActionFunction, json, LoaderFunction, redirect } from '@remix-run/node' +import { login, register, getUser } from '~/utils/auth.server' export const loader: LoaderFunction = async ({ request }) => { // If there's already a user in the session, redirect to the home page - return (await getUser(request)) ? redirect("/") : null; -}; + return (await getUser(request)) ? redirect('/') : null +} // ... ``` - ## Add form validation Great! Your sign in and sign up forms are working and you have set up authorization and redirects on your private routes. You're almost at the finish line! @@ -1159,7 +1107,6 @@ export function FormField({ } ``` - This component will now take in an error message. When the user starts to type in that field, if any error message was being shown it will be cleared out. In the login form you will need to get access to the data returned from the action using Remix's [`useActionData`](https://remix.run/docs/en/v1/api/remix#useactiondata) hook in order to pull out the error messages. @@ -1168,28 +1115,27 @@ In the login form you will need to get access to the data returned from the acti // app/routes/login.tsx // ... -import { useActionData } from "@remix-run/react"; -import { useRef, useEffect } from "react"; +import { useActionData } from '@remix-run/react' +import { useRef, useEffect } from 'react' // ... export default function Login() { // ... // 1 - const actionData = useActionData(); + const actionData = useActionData() // 2 - const firstLoad = useRef(true); - const [errors, setErrors] = useState(actionData?.errors || {}); - const [formError, setFormError] = useState(actionData?.error || ""); + const firstLoad = useRef(true) + const [errors, setErrors] = useState(actionData?.errors || {}) + const [formError, setFormError] = useState(actionData?.error || '') // 3 const [formData, setFormData] = useState({ - email: actionData?.fields?.email || "", - password: actionData?.fields?.password || "", - firstName: actionData?.fields?.lastName || "", - lastName: actionData?.fields?.firstName || "", - }); + email: actionData?.fields?.email || '', + password: actionData?.fields?.password || '', + firstName: actionData?.fields?.lastName || '', + lastName: actionData?.fields?.firstName || '', + }) // ... } ``` - This code adds the following: 1. Hooks into the data returned from the `action` function. @@ -1206,30 +1152,27 @@ export default function Login() { useEffect(() => { if (!firstLoad.current) { const newState = { - email: "", - password: "", - firstName: "", - lastName: "", - }; - setErrors(newState); - setFormError(""); - setFormData(newState); + email: '', + password: '', + firstName: '', + lastName: '', + } + setErrors(newState) + setFormError('') + setFormData(newState) } - }, [action]); + }, [action]) useEffect(() => { if (!firstLoad.current) { - setFormError(""); + setFormError('') } - }, [formData]); + }, [formData]) - useEffect(() => { - firstLoad.current = false; - }, []); + useEffect(() => { firstLoad.current = false }, []) } // ... ``` - With those in place, you can finally let your form and fields know which errors to display. ```tsx @@ -1279,7 +1222,6 @@ diff // app/routes/login.tsx // ... ``` - Now you should see error messages and form resets working properly on your sign up and sign in forms! ![](/fullstack-remix-prisma-mongodb-2-ZTmOy58p4re8/imgs/error-message.png) @@ -1295,3 +1237,4 @@ Now you should see error messages and form resets working properly on your sign - How to store and query your data using Prisma when creating and authenticating users. In the next section of this series you will build the home page of Kudos and the kudos-sharing functionality. You will also add searching and filtering capabilities to the kudos feed. + diff --git a/apps/blog/content/blog/fullstack-remix-prisma-mongodb-3-By5pmN5Nzo1v/index.mdx b/apps/blog/content/blog/fullstack-remix-prisma-mongodb-3-By5pmN5Nzo1v/index.mdx index 278b97bf18..a860656291 100644 --- a/apps/blog/content/blog/fullstack-remix-prisma-mongodb-3-By5pmN5Nzo1v/index.mdx +++ b/apps/blog/content/blog/fullstack-remix-prisma-mongodb-3-By5pmN5Nzo1v/index.mdx @@ -79,19 +79,18 @@ This new file should export a function component called `Home` for now, along wi ```tsx // app/routes/home.tsx -import { LoaderFunction } from "@remix-run/node"; -import { requireUserId } from "~/utils/auth.server"; +import { LoaderFunction } from '@remix-run/node' +import { requireUserId } from '~/utils/auth.server' export const loader: LoaderFunction = async ({ request }) => { - await requireUserId(request); - return null; -}; + await requireUserId(request) + return null +} export default function Home() { - return

Home Page

; + return

Home Page

} ``` - This `/home` route will act as the main page of your application rather than the base url. Currently, the `app/routes/index.tsx` file _(the `/` route)_ renders a React component. That route should only ever redirect a user: either to the `/home` or `/login` route. Set up a [resource route](https://remix.run/docs/en/v1/guides/resource-routes) in its place to achieve that functionality. @@ -105,15 +104,14 @@ Delete the existing `app/routes/index.tsx` file and replace it with an `index.ts ```ts // app/routes/index.ts -import { LoaderFunction, redirect } from "@remix-run/node"; -import { requireUserId } from "~/utils/auth.server"; +import { LoaderFunction, redirect } from '@remix-run/node' +import { requireUserId } from '~/utils/auth.server' export const loader: LoaderFunction = async ({ request }) => { - await requireUserId(request); - return redirect("/home"); -}; + await requireUserId(request) + return redirect('/home') +} ``` - > **Note**: The file's extension was changed to `.ts` because this route will never render a component. The `loader` above will first check if a user is logged in when they hit the `/` route. The `requireUserId` function will redirect to `/login` if there isn't a valid session. @@ -148,25 +146,24 @@ export function UserPanel() {
- ); + ) } ``` - This creates the side panel that will contain the list of users. The component is _static_ though, meaning it does not perform any actions or vary in any way. Before making this component more _dynamic_ by adding a list of users, import it into the `app/routes/home.tsx` page and render it onto the page. ```tsx // app/routes/home.tsx -import { LoaderFunction } from "@remix-run/node"; -import { requireUserId } from "~/utils/auth.server"; -import { Layout } from "~/components/layout"; -import { UserPanel } from "~/components/user-panel"; +import { LoaderFunction } from '@remix-run/node' +import { requireUserId } from '~/utils/auth.server' +import { Layout } from '~/components/layout' +import { UserPanel } from '~/components/user-panel' export const loader: LoaderFunction = async ({ request }) => { - await requireUserId(request); - return null; // <- A loader always has to return some value, even if that is null -}; + await requireUserId(request) + return null // <- A loader always has to return some value, even if that is null +} export default function Home() { return ( @@ -175,10 +172,9 @@ export default function Home() {

- ); + ) } ``` - The code above imports the new component and the `Layout` component, then renders the new component within the layout. ![](/fullstack-remix-prisma-mongodb-3-By5pmN5Nzo1v/imgs/user-panel.png) @@ -201,13 +197,12 @@ export const getOtherUsers = async (userId: string) => { }, orderBy: { profile: { - firstName: "asc", + firstName: 'asc', }, }, - }); -}; + }) +} ``` - The `where` filter excludes any documents whose `id` matches the `userId` parameter. This will be used to grab every `user` _except the currently logged in user_. > **Note**: Notice how easy it is to sort by fields within an embedded document? @@ -236,7 +231,6 @@ export const loader: LoaderFunction = async ({ request }) => { // ... ``` - > **Note**: Any code that is run within a [`loader`](https://remix.run/docs/en/v1/guides/data-loading) function is not exposed to the client-side code. You can thank Remix for this awesome feature! If you had any users in your database and outputted the `users` variable inside of the loader, you should see a list of all users _except yourself_. @@ -253,13 +247,12 @@ Set up a new `users` prop in the `UserPanel` component. ```tsx // app/components/user-panel.tsx -import { User } from "@prisma/client"; +import { User } from '@prisma/client' export function UserPanel({ users }: { users: User[] }) { // ... } ``` - The `User` type used here was generated by Prisma and is available via Prisma Client. Remix works very nicely with Prisma because it is extremely easy to achieve end-to-end type safety in a fullstack framework. > **Note**: End-to-end type safety occurs when the types across your entire stack are kept in sync as the shape of your data changes. @@ -268,10 +261,10 @@ In `app/routes/home.tsx` you may now supply the users to the `UserPanel` compone ```tsx // app/routes/home.tsx -import { useLoaderData } from "@remix-run/react"; +import { useLoaderData } from '@remix-run/react' // ... export default function Home() { - const { users } = useLoaderData(); + const { users } = useLoaderData() return (
@@ -279,11 +272,10 @@ export default function Home() {
- ); + ) } // ... ``` - The component will now have the `users` to work with. Now it needs to display them. ## Build the user display component @@ -294,12 +286,12 @@ Create a new file in `app/components` named `user-circle.tsx` and add the follow ```tsx // app/components/user-circle.tsx -import { Profile } from "@prisma/client"; +import { Profile } from '@prisma/client' interface props { - profile: Profile; - className?: string; - onClick?: (...args: any) => any; + profile: Profile + className?: string + onClick?: (...args: any) => any } export function UserCircle({ profile, onClick, className }: props) { @@ -313,10 +305,9 @@ export function UserCircle({ profile, onClick, className }: props) { {profile.lastName.charAt(0).toUpperCase()} - ); + ) } ``` - This component uses the `Profile` type generated by Prisma because you will be passing in only the `profile` data from the `user` documents. It also has some configurable options that allow you to provide a click action and add additional classes to customize its style. @@ -339,7 +330,6 @@ export function UserPanel({ users }: { users: User[] }) { ) } ``` - Beautiful! Your users will now be rendered in a nice column on the left side of the home page. The only non-functional piece of the side panel at this point is the sign out button. ![](/fullstack-remix-prisma-mongodb-3-By5pmN5Nzo1v/imgs/user-list.png) @@ -357,7 +347,6 @@ import { logout } from "~/utils/auth.server"; export const action: ActionFunction = async ({ request }) => logout(request); export const loader: LoaderFunction = async () => redirect("/"); ``` - This route handles two possible actions: POST and GET - `POST`: This will trigger the `logout` function written in the previous part of this series. @@ -385,7 +374,6 @@ export function UserPanel({ users }: props) { ) } ``` - Your users can now sign out of the application! The user whose session is associated with the `POST` request will be signed out and their session destroyed. ## Add the ability to send kudos @@ -408,6 +396,7 @@ There are a couple of data points you will be saving and displaying that are not 2. Add a 1:n relation in the `User` model that defines the kudos a user is the _author_ of. Also add a similar relation that defines the kudos a user is a _recipient_ of. 3. Add `enum`s for emojis, departments, and colors to define the available options. + ```prisma // prisma/schema.prisma @@ -447,7 +436,6 @@ model Kudo { style KudoStyle? } ``` - ```prisma diff // prisma/schema.prisma @@ -465,7 +453,6 @@ model Kudo { + recipientId String @db.ObjectId } ``` - > **Note:** After applying `@default` to a field, if a record in your collection does not have the new required field if will be updated to include that field with the default value the next time it is read. That's all you'll need to update for now. Run `npx prisma db push`, which will automatically re-generate `PrismaClient`. @@ -493,23 +480,22 @@ In that new file export a `loader` function and a React component that renders s ```tsx // app/routes/home/kudo.$userId.tsx -import { json, LoaderFunction } from "@remix-run/node"; -import { useLoaderData } from "@remix-run/react"; +import { json, LoaderFunction } from '@remix-run/node' +import { useLoaderData } from '@remix-run/react' // 1 export const loader: LoaderFunction = async ({ request, params }) => { // 2 - const { userId } = params; - return json({ userId }); -}; + const { userId } = params + return json({ userId }) +} export default function KudoModal() { // 3 - const data = useLoaderData(); - return

User: {data.userId}

; + const data = useLoaderData() + return

User: {data.userId}

} ``` - The code above does a few things: 1. It pulls the `params` field from the loader function. @@ -538,7 +524,6 @@ export default function Home() { } ``` - If you head over to [http://localhost:3000/home/kudo/123](http://localhost:3000/home/kudo/123), you should now see the text "User: 123" displayed at the very top of the page. If you change the value in the URL to something other than `123` you should see that change reflected on the screen. ![](/fullstack-remix-prisma-mongodb-3-By5pmN5Nzo1v/imgs/nested-route.png) @@ -557,10 +542,9 @@ export const getUserById = async (userId: string) => { where: { id: userId, }, - }); -}; + }) +} ``` - The query above finds the unique record in the database with the given `id`. The [`findUnique`](https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#findunique) function allows you to filter your query using _uniquely identifying_ fields, or fields with values that _must_ be unique to that record within your database. Next: @@ -570,23 +554,22 @@ Next: ```tsx // app/routes/home/kudo.$userId.tsx -import { json, LoaderFunction, redirect } from "@remix-run/node"; -import { useLoaderData } from "@remix-run/react"; -import { getUserById } from "~/utils/user.server"; +import { json, LoaderFunction, redirect } from '@remix-run/node' +import { useLoaderData } from '@remix-run/react' +import { getUserById } from '~/utils/user.server' export const loader: LoaderFunction = async ({ request, params }) => { - const { userId } = params; + const { userId } = params - if (typeof userId !== "string") { - return redirect("/home"); + if (typeof userId !== 'string') { + return redirect('/home') } - const recipient = await getUserById(userId); - return json({ recipient }); -}; + const recipient = await getUserById(userId) + return json({ recipient }) +} // ... ``` - Next, you need a way to navigate to a nested route with a valid `id`. In `app/components/user-panel.tsx`, the file where you are rendering the user list, import the `useNavigation` hook Remix provides and use it to navigate to the nested route when a user is clicked. @@ -613,7 +596,6 @@ export function UserPanel({ users }: props) { ) } ``` - Now when your users click on another user in that panel, they will be navigated to a sub-route with that user's information. ![](/fullstack-remix-prisma-mongodb-3-By5pmN5Nzo1v/imgs/nested-route-names.gif) @@ -633,52 +615,51 @@ In `app/components` create a new file named `portal.tsx` with the following cont ```tsx // app/components/portal.tsx -import { createPortal } from "react-dom"; -import { useState, useEffect } from "react"; +import { createPortal } from 'react-dom' +import { useState, useEffect } from 'react' interface props { - children: React.ReactNode; - wrapperId: string; + children: React.ReactNode + wrapperId: string } // 1 const createWrapper = (wrapperId: string) => { - const wrapper = document.createElement("div"); - wrapper.setAttribute("id", wrapperId); - document.body.appendChild(wrapper); - return wrapper; -}; + const wrapper = document.createElement('div') + wrapper.setAttribute('id', wrapperId) + document.body.appendChild(wrapper) + return wrapper +} export const Portal: React.FC = ({ children, wrapperId }) => { - const [wrapper, setWrapper] = useState(null); + const [wrapper, setWrapper] = useState(null) useEffect(() => { // 2 - let element = document.getElementById(wrapperId); - let created = false; + let element = document.getElementById(wrapperId) + let created = false if (!element) { - created = true; - element = createWrapper(wrapperId); + created = true + element = createWrapper(wrapperId) } - setWrapper(element); + setWrapper(element) // 3 return () => { if (created && element?.parentNode) { - element.parentNode.removeChild(element); + element.parentNode.removeChild(element) } - }; - }, [wrapperId]); + } + }, [wrapperId]) - if (wrapper === null) return null; + if (wrapper === null) return null // 4 - return createPortal(children, wrapper); -}; + return createPortal(children, wrapper) +} ``` - Here's an explanation of what is going on in this component: 1. A function is defined that generates a `div` with an `id`. That element is then attached to the document's `body`. @@ -705,7 +686,6 @@ export default function KudoModal() { + return {/* ... */} } ``` - If you navigate to your nested route, you will see a `div` with an `id` of `"kudo-modal"` is now rendered as a direct child of the `body` rather than where the nested route is being rendered in the DOM tree. ![](/fullstack-remix-prisma-mongodb-3-By5pmN5Nzo1v/imgs/portal.gif) @@ -725,42 +705,39 @@ Add the following code to create the `Modal` component: ```tsx // app/components/modal.tsx -import { Portal } from "./portal"; -import { useNavigate } from "@remix-run/react"; +import { Portal } from './portal' +import { useNavigate } from '@remix-run/react' interface props { - children: React.ReactNode; - isOpen: boolean; - ariaLabel?: string; - className?: string; + children: React.ReactNode + isOpen: boolean + ariaLabel?: string + className?: string } export const Modal: React.FC = ({ children, isOpen, ariaLabel, className }) => { - const navigate = useNavigate(); - if (!isOpen) return null; + const navigate = useNavigate() + if (!isOpen) return null return (
navigate("/home")} + onClick={() => navigate('/home')} >
-
+
{/* This is where the modal content is rendered */} {children}
- ); -}; + ) +} ``` - The `Portal` component is imported and wraps the entirety of the modal to ensure it is rendered in a safe location. The modal is then defined as a fixed element on the screen with an opaque backdrop using various TailwindCSS helpers. @@ -790,7 +767,6 @@ export default function KudoModal() { ) } ``` - The modal should now open up when a user from the side panel is clicked. ![](/fullstack-remix-prisma-mongodb-3-By5pmN5Nzo1v/imgs/working-modal.gif) @@ -810,49 +786,49 @@ export const loader: LoaderFunction = async ({ request, params }) => { } // ... ``` - Then make the following changes to the `KudoModal` function in that file: ```tsx // app/routes/home/kudo.$userId.tsx // 1 -import { useLoaderData, useActionData } from "@remix-run/react"; -import { UserCircle } from "~/components/user-circle"; -import { useState } from "react"; -import { KudoStyle } from "@prisma/client"; +import { + useLoaderData, + useActionData +} from '@remix-run/react' +import { UserCircle } from '~/components/user-circle' +import { useState } from 'react' +import { KudoStyle } from '@prisma/client' // ... export default function KudoModal() { - // 2 - const actionData = useActionData(); - const [formError] = useState(actionData?.error || ""); - const [formData, setFormData] = useState({ - message: "", - style: { - backgroundColor: "RED", - textColor: "WHITE", - emoji: "THUMBSUP", - } as KudoStyle, - }); +// 2 +const actionData = useActionData() +const [formError] = useState(actionData?.error || '') +const [formData, setFormData] = useState({ + message: '', + style: { + backgroundColor: 'RED', + textColor: 'WHITE', + emoji: 'THUMBSUP', + } as KudoStyle, +}) // 3 - const handleChange = ( - e: React.ChangeEvent, - field: string, - ) => { - setFormData((data) => ({ ...data, [field]: e.target.value })); - }; +const handleChange = (e: React.ChangeEvent, field: string) => { + setFormData(data => ({ ...data, [field]: e.target.value })) +} - const { recipient, user } = useLoaderData(); + const { + recipient, + user + } = useLoaderData() // 4 return ( -
- {formError} -
+
{formError}
@@ -863,8 +839,7 @@ export default function KudoModal() {

{recipient.profile.department && ( - {recipient.profile.department[0].toUpperCase() + - recipient.profile.department.toLowerCase().slice(1)} + {recipient.profile.department[0].toUpperCase() + recipient.profile.department.toLowerCase().slice(1)} )}
@@ -873,7 +848,7 @@ export default function KudoModal() { name="message" className="w-full rounded-xl h-40 p-4" value={formData.message} - onChange={(e) => handleChange(e, "message")} + onChange={e => handleChange(e, 'message')} placeholder={`Say something nice about ${recipient.profile.firstName}...`} />
@@ -895,10 +870,9 @@ export default function KudoModal() {
- ); + ) } ``` - This was a big chunk of new code, so take a look at what changes were made: 1. Imports a few components and hooks you will need. @@ -917,23 +891,23 @@ Create a new file in `app/components` named `select-box.tsx` that exports a `Sel interface props { options: { - name: string; - value: any; - }[]; - className?: string; - containerClassName?: string; - id?: string; - name?: string; - label?: string; - value?: any; - onChange?: (...args: any) => any; + name: string + value: any + }[] + className?: string + containerClassName?: string + id?: string + name?: string + label?: string + value?: any + onChange?: (...args: any) => any } export function SelectBox({ options = [], onChange = () => {}, - className = "", - containerClassName = "", + className = '', + containerClassName = '', name, id, value, @@ -945,14 +919,8 @@ export function SelectBox({ {label}
- + {options.map(option => ( @@ -969,10 +937,9 @@ export function SelectBox({
- ); + ) } ``` - This component is similar to the `FormField` component in that it is a _controlled component_ that takes in some configuration and allows its state to be managed by its parent. These select boxes will need to be populated with the color and emoji options. Create a helper file to hold the possible options at `app/utils/constants.ts`: @@ -981,28 +948,27 @@ These select boxes will need to be populated with the color and emoji options. C // app/utils/constants.ts export const colorMap = { - RED: "text-red-400", - GREEN: "text-green-400", - BLUE: "text-blue-400", - WHITE: "text-white", - YELLOW: "text-yellow-300", -}; + RED: 'text-red-400', + GREEN: 'text-green-400', + BLUE: 'text-blue-400', + WHITE: 'text-white', + YELLOW: 'text-yellow-300', +} export const backgroundColorMap = { - RED: "bg-red-400", - GREEN: "bg-green-400", - BLUE: "bg-blue-400", - WHITE: "bg-white", - YELLOW: "bg-yellow-300", -}; + RED: 'bg-red-400', + GREEN: 'bg-green-400', + BLUE: 'bg-blue-400', + WHITE: 'bg-white', + YELLOW: 'bg-yellow-300', +} export const emojiMap = { - THUMBSUP: "👍", - PARTY: "🎉", - HANDSUP: "🙌🏻", -}; + THUMBSUP: '👍', + PARTY: '🎉', + HANDSUP: '🙌🏻', +} ``` - Now in `app/routes/home/kudo.$userId.tsx`, import the `SelectBox` component and the constants. Also add the variables and functions requried to hook them up to the form's state and render the `SelectBox` components in place of the `{/* Select Boxes Go Here */}` comment: ```tsx @@ -1077,7 +1043,6 @@ export default function KudoModal() { ) } ``` - The select boxes will now appear with all of the possible options. ![](/fullstack-remix-prisma-mongodb-3-By5pmN5Nzo1v/imgs/select-boxes.png) @@ -1091,40 +1056,33 @@ Create a new file at `app/components` named `kudo.tsx`: ```tsx // app/components/kudo.tsx -import { UserCircle } from "~/components/user-circle"; -import { Profile, Kudo as IKudo } from "@prisma/client"; -import { colorMap, backgroundColorMap, emojiMap } from "~/utils/constants"; +import { UserCircle } from '~/components/user-circle' +import { Profile, Kudo as IKudo } from '@prisma/client' +import { colorMap, backgroundColorMap, emojiMap } from '~/utils/constants' export function Kudo({ profile, kudo }: { profile: Profile; kudo: Partial }) { return (
-

+

{profile.firstName} {profile.lastName}

-

- {kudo.message} -

+

{kudo.message}

- {emojiMap[kudo.style?.emoji || "THUMBSUP"]} + {emojiMap[kudo.style?.emoji || 'THUMBSUP']}
- ); + ) } ``` - This component takes in the props: - `profile`: The `profile` data from the recipients `user` document. @@ -1157,7 +1115,6 @@ export default function KudoModal() { ) } ``` - The preview will now be rendered, displaying the currently logged in user's information and the styled message they are going to send. ![](/fullstack-remix-prisma-mongodb-3-By5pmN5Nzo1v/imgs/kudo-preview.gif) @@ -1173,15 +1130,10 @@ In this file, export a `createKudo` method that takes in the kudo form data, the ```ts // app/utils/kudos.server.ts -import { prisma } from "./prisma.server"; -import { KudoStyle } from "@prisma/client"; +import { prisma } from './prisma.server' +import { KudoStyle } from '@prisma/client' -export const createKudo = async ( - message: string, - userId: string, - recipientId: string, - style: KudoStyle, -) => { +export const createKudo = async (message: string, userId: string, recipientId: string, style: KudoStyle) => { await prisma.kudo.create({ data: { // 1 @@ -1199,10 +1151,9 @@ export const createKudo = async ( }, }, }, - }); -}; + }) +} ``` - The query above does the following: 1. Passes in the `message` string and `style` embedded document. @@ -1274,7 +1225,6 @@ import { // ... ``` - Here's an overview of the snippet above: 1. Imports the new `createKudo` function, along with a few types generated by Prisma, the `ActionFunction` type from Remix, and the `requireUserId` function you wrote previously. @@ -1295,7 +1245,7 @@ In `app/utils/kudos.server.ts` create and export a new function named `getFilter // app/utils/kudos.server.ts // 👇 Added the Prisma namespace in the import -import { KudoStyle, Prisma } from "@prisma/client"; +import { KudoStyle, Prisma } from '@prisma/client' // ... @@ -1322,10 +1272,9 @@ export const getFilteredKudos = async ( recipientId: userId, ...whereFilter, }, - }); -}; + }) +} ``` - The function above takes in a few different parameters. Here is what those are: - `userId`: The `id` of the user whose kudos the query should retrieve. @@ -1338,24 +1287,24 @@ Now in `app/routes/home.tsx`, import that function and invoke it in the `loader` ```tsx // app/routes/home.tsx -import { getFilteredKudos } from "~/utils/kudos.server"; -import { Kudo } from "~/components/kudo"; -import { Kudo as IKudo, Profile } from "@prisma/client"; +import { getFilteredKudos } from '~/utils/kudos.server' +import { Kudo } from '~/components/kudo' +import { Kudo as IKudo, Profile } from '@prisma/client' interface KudoWithProfile extends IKudo { author: { - profile: Profile; - }; + profile: Profile + } } export const loader: LoaderFunction = async ({ request }) => { // ... - const kudos = await getFilteredKudos(userId, {}, {}); - return json({ users, kudos }); -}; + const kudos = await getFilteredKudos(userId, {}, {}) + return json({ users, kudos }) +} export default function Home() { - const { users, kudos } = useLoaderData(); + const { users, kudos } = useLoaderData() return ( @@ -1374,10 +1323,9 @@ export default function Home() { - ); + ) } ``` - The `Kudo` and `Profile` types generated by Prisma are combined to create a `KudoWithProfile` type. This is needed because your array has kudos that include the profile data from the author. If you send a couple of kudos to an account and log in to that account, you should now see a rendered list of kudos on your feed. @@ -1393,16 +1341,16 @@ Create a new file in `app/components` named `search-bar.tsx`. This component wil ```tsx // app/components/search-bar.tsx -import { useNavigate, useSearchParams } from "@remix-run/react"; +import { useNavigate, useSearchParams } from '@remix-run/react' export function SearchBar() { - const navigate = useNavigate(); - let [searchParams] = useSearchParams(); + const navigate = useNavigate() + let [searchParams] = useSearchParams() const clearFilters = () => { - searchParams.delete("filter"); - navigate("/home"); - }; + searchParams.delete('filter') + navigate('/home') + } return (
@@ -1428,7 +1376,7 @@ export function SearchBar() { > Search - {searchParams.get("filter") && ( + {searchParams.get('filter') && (
// [!code ++] - - // [!code ++] -

Already have an account? Sign in here.

- // [!code ++] + // [!code ++] +

Already have an account? Sign in here.

// [!code ++] @@ -599,14 +602,17 @@ export const prerender = false;

Sign In

-
- // [!code ++] // [!code ++] - // [!code ++] + // [!code ++] + // [!code ++] + // [!code ++] // [!code ++] -
- // [!code ++] -

Don't have an account? Sign up here.

- // [!code ++] + // [!code ++] +

Don't have an account? Sign up here.

// [!code ++]
diff --git a/apps/docs/content/docs/guides/database/multiple-databases.mdx b/apps/docs/content/docs/guides/database/multiple-databases.mdx index 0a3e09f3b3..1dc7745b33 100644 --- a/apps/docs/content/docs/guides/database/multiple-databases.mdx +++ b/apps/docs/content/docs/guides/database/multiple-databases.mdx @@ -1,10 +1,10 @@ --- title: Multiple databases -description: "Learn how to use multiple Prisma Clients in a single app to connect to multiple databases, handle migrations, and deploy your application to Vercel" +description: 'Learn how to use multiple Prisma Clients in a single app to connect to multiple databases, handle migrations, and deploy your application to Vercel' image: /img/guides/multiple-databases.png url: /guides/database/multiple-databases metaTitle: How to use Prisma ORM with multiple databases in a single app -metaDescription: "Learn how to use multiple Prisma Clients in a single app to connect to multiple databases, handle migrations, and deploy your application to Vercel." +metaDescription: 'Learn how to use multiple Prisma Clients in a single app to connect to multiple databases, handle migrations, and deploy your application to Vercel.' --- ## Introduction diff --git a/apps/docs/content/docs/guides/deployment/bun-workspaces.mdx b/apps/docs/content/docs/guides/deployment/bun-workspaces.mdx index 0bbcfa68c1..948f57237b 100644 --- a/apps/docs/content/docs/guides/deployment/bun-workspaces.mdx +++ b/apps/docs/content/docs/guides/deployment/bun-workspaces.mdx @@ -88,6 +88,7 @@ This command: - Creates a `prisma.config.ts` file for configuring Prisma. - Creates a `.env` file with a local `DATABASE_URL`. + Create a Prisma Postgres database and replace the generated `DATABASE_URL` in your `.env` file with the `postgres://...` connection string from the CLI output: ```bash @@ -123,8 +124,7 @@ Add a `scripts` section to your database `package.json` (Bun init may not add on ```json title="database/package.json" { - "scripts": { - // [!code ++] + "scripts": { // [!code ++] "db:generate": "prisma generate", // [!code ++] "db:migrate": "prisma migrate dev", // [!code ++] "db:deploy": "prisma migrate deploy", // [!code ++] diff --git a/apps/docs/content/docs/guides/deployment/pnpm-workspaces.mdx b/apps/docs/content/docs/guides/deployment/pnpm-workspaces.mdx index 6289d1d3fb..9e3698501e 100644 --- a/apps/docs/content/docs/guides/deployment/pnpm-workspaces.mdx +++ b/apps/docs/content/docs/guides/deployment/pnpm-workspaces.mdx @@ -121,6 +121,7 @@ This command: - Creates a `prisma` directory containing a `schema.prisma` file for your database models. - Creates a `.env` file with a local `DATABASE_URL`. + Create a Prisma Postgres database and replace the generated `DATABASE_URL` in your `.env` file with the `postgres://...` connection string from the CLI output: ```bash diff --git a/apps/docs/content/docs/guides/deployment/turborepo.mdx b/apps/docs/content/docs/guides/deployment/turborepo.mdx index 118b4aa17d..a0181517ac 100644 --- a/apps/docs/content/docs/guides/deployment/turborepo.mdx +++ b/apps/docs/content/docs/guides/deployment/turborepo.mdx @@ -1,10 +1,10 @@ --- title: Turborepo -description: "Learn step-by-step how to integrate Prisma ORM with Turborepo to build modular, scalable monorepo architectures efficiently" +description: 'Learn step-by-step how to integrate Prisma ORM with Turborepo to build modular, scalable monorepo architectures efficiently' image: /img/guides/prisma-turborepo-setup.png url: /guides/deployment/turborepo metaTitle: How to use Prisma ORM and Prisma Postgres with Turborepo -metaDescription: "Learn step-by-step how to integrate Prisma ORM with Turborepo to build modular, scalable monorepo architectures efficiently." +metaDescription: 'Learn step-by-step how to integrate Prisma ORM with Turborepo to build modular, scalable monorepo architectures efficiently.' --- Prisma is a powerful ORM for managing databases, and [Turborepo](https://turborepo.dev/docs) simplifies monorepo workflows. By combining these tools, you can create a scalable, modular architecture for your projects. @@ -209,16 +209,13 @@ Let's also add these scripts to `turbo.json` in the root and ensure that `DATABA "cache": false, "persistent": true }, - "db:generate": { - // [!code ++] + "db:generate": { // [!code ++] "cache": false // [!code ++] }, // [!code ++] - "db:migrate": { - // [!code ++] + "db:migrate": { // [!code ++] "cache": false // [!code ++] }, // [!code ++] - "db:deploy": { - // [!code ++] + "db:deploy": { // [!code ++] "cache": false // [!code ++] } // [!code ++] } diff --git a/apps/docs/content/docs/guides/frameworks/hono.mdx b/apps/docs/content/docs/guides/frameworks/hono.mdx index 3f3f2d4c49..809d2632ab 100644 --- a/apps/docs/content/docs/guides/frameworks/hono.mdx +++ b/apps/docs/content/docs/guides/frameworks/hono.mdx @@ -41,7 +41,7 @@ npm create hono@latest - _Which template do you want to use?_ `nodejs` - _Install dependencies? (recommended)_ `Yes` - _Which package manager do you want to use?_ `npm` - ::: +::: ## 2. Install and configure Prisma diff --git a/apps/docs/content/docs/guides/frameworks/react-router-7.mdx b/apps/docs/content/docs/guides/frameworks/react-router-7.mdx index ce32bb00d3..8bc7f56270 100644 --- a/apps/docs/content/docs/guides/frameworks/react-router-7.mdx +++ b/apps/docs/content/docs/guides/frameworks/react-router-7.mdx @@ -31,7 +31,7 @@ You'll be prompted to select the following, select `Yes` for both: - _Initialize a new git repository?_ `Yes` - _Install dependencies with npm?_ `Yes` - ::: +::: Now, navigate to the project directory: diff --git a/apps/docs/content/docs/guides/integrations/ai-sdk.mdx b/apps/docs/content/docs/guides/integrations/ai-sdk.mdx index 5963708f4b..3bd3af5184 100644 --- a/apps/docs/content/docs/guides/integrations/ai-sdk.mdx +++ b/apps/docs/content/docs/guides/integrations/ai-sdk.mdx @@ -1,10 +1,10 @@ --- title: AI SDK (with Next.js) -description: "Build a chat application with AI SDK, Prisma, and Next.js to store chat sessions and messages" +description: 'Build a chat application with AI SDK, Prisma, and Next.js to store chat sessions and messages' image: /img/guides/prisma-ai-sdk-nextjs-cover.png url: /guides/integrations/ai-sdk -metaTitle: "How to use AI SDK with Prisma ORM, Prisma Postgres, and Next.js for chat applications" -metaDescription: "Build a chat application with AI SDK, Prisma, and Next.js to store chat sessions and messages" +metaTitle: 'How to use AI SDK with Prisma ORM, Prisma Postgres, and Next.js for chat applications' +metaDescription: 'Build a chat application with AI SDK, Prisma, and Next.js to store chat sessions and messages' --- ## Introduction diff --git a/apps/docs/content/docs/guides/integrations/datadog.mdx b/apps/docs/content/docs/guides/integrations/datadog.mdx index 0f2249337d..ac26131e60 100644 --- a/apps/docs/content/docs/guides/integrations/datadog.mdx +++ b/apps/docs/content/docs/guides/integrations/datadog.mdx @@ -1,10 +1,10 @@ --- title: Datadog -description: "Learn how to configure Datadog tracing for a Prisma ORM project. Capture spans for every query using the @prisma/instrumentation package, dd-trace, and view them in Datadog" +description: 'Learn how to configure Datadog tracing for a Prisma ORM project. Capture spans for every query using the @prisma/instrumentation package, dd-trace, and view them in Datadog' image: /img/guides/datadog-tracing-prisma.png url: /guides/integrations/datadog metaTitle: How to set up Datadog tracing with Prisma ORM and Prisma Postgres -metaDescription: "Learn how to configure Datadog tracing for a Prisma ORM project. Capture spans for every query using the @prisma/instrumentation package, dd-trace, and view them in Datadog." +metaDescription: 'Learn how to configure Datadog tracing for a Prisma ORM project. Capture spans for every query using the @prisma/instrumentation package, dd-trace, and view them in Datadog.' --- ## Introduction diff --git a/apps/docs/content/docs/guides/integrations/embed-studio.mdx b/apps/docs/content/docs/guides/integrations/embed-studio.mdx index bcab8a5928..dc91bef7ba 100644 --- a/apps/docs/content/docs/guides/integrations/embed-studio.mdx +++ b/apps/docs/content/docs/guides/integrations/embed-studio.mdx @@ -520,6 +520,7 @@ If you are using SQLite or MySQL, you can still embed Studio, but your `/api/stu To do this, create a new folder called `api` inside the `app` directory. Inside it, add a `studio` folder with a `route.ts` file. This file will handle all requests sent to `/api/studio` and act as the bridge between the Studio component in your frontend and the database in your backend: + ```typescript title="app/api/studio/route.ts" tab="PostgreSQL" import "dotenv/config"; import { createPrismaPostgresHttpClient } from "@prisma/studio-core/data/ppg"; diff --git a/apps/docs/content/docs/guides/integrations/permit-io.mdx b/apps/docs/content/docs/guides/integrations/permit-io.mdx index c5c072e0e2..68e6fade04 100644 --- a/apps/docs/content/docs/guides/integrations/permit-io.mdx +++ b/apps/docs/content/docs/guides/integrations/permit-io.mdx @@ -640,6 +640,7 @@ If everything is set up correctly, the console will display: 🔐 ReBAC filtering is now active ``` + ### 9.3 Test your API You can simulate requests as different users by setting the `x-user-email` header. This mimics logged-in users with access to specific projects. diff --git a/apps/docs/content/docs/guides/integrations/pgfence.mdx b/apps/docs/content/docs/guides/integrations/pgfence.mdx index a7dc6c0518..f751a31e5b 100644 --- a/apps/docs/content/docs/guides/integrations/pgfence.mdx +++ b/apps/docs/content/docs/guides/integrations/pgfence.mdx @@ -49,12 +49,12 @@ pgfence parses every SQL statement and reports the lock mode, risk level, and an pgfence assigns a risk level to each statement based on the PostgreSQL lock it acquires: -| Risk level | Meaning | -| ------------ | ----------------------------------------------------------------------------------------------------------------- | -| **LOW** | Safe operations with minimal locking (e.g., `ADD COLUMN` with a constant default on PG 11+) | -| **MEDIUM** | Operations that block writes but not reads (e.g., `CREATE INDEX` without `CONCURRENTLY`) | -| **HIGH** | Operations that block writes and competing DDL, but not plain reads (e.g., `ADD FOREIGN KEY` without `NOT VALID`) | -| **CRITICAL** | Operations that take `ACCESS EXCLUSIVE` locks on large tables (e.g., `DROP TABLE`, `TRUNCATE`) | +| Risk level | Meaning | +|------------|---------| +| **LOW** | Safe operations with minimal locking (e.g., `ADD COLUMN` with a constant default on PG 11+) | +| **MEDIUM** | Operations that block writes but not reads (e.g., `CREATE INDEX` without `CONCURRENTLY`) | +| **HIGH** | Operations that block writes and competing DDL, but not plain reads (e.g., `ADD FOREIGN KEY` without `NOT VALID`) | +| **CRITICAL** | Operations that take `ACCESS EXCLUSIVE` locks on large tables (e.g., `DROP TABLE`, `TRUNCATE`) | Here is an example of pgfence analyzing a migration that adds an index without `CONCURRENTLY`: diff --git a/apps/docs/content/docs/guides/integrations/shopify.mdx b/apps/docs/content/docs/guides/integrations/shopify.mdx index fcdfa36353..c1d614679c 100644 --- a/apps/docs/content/docs/guides/integrations/shopify.mdx +++ b/apps/docs/content/docs/guides/integrations/shopify.mdx @@ -162,23 +162,23 @@ npx create-db Copy the connection string from the CLI output. It should look similar to this: -```text -DATABASE_URL="postgresql://user:password@host:5432/database?sslmode=require" -``` + ```text + DATABASE_URL="postgresql://user:password@host:5432/database?sslmode=require" + ``` Replace the generated `DATABASE_URL` in your `.env` file with the value from `npx create-db`. Apply your database schema: -```npm -npx prisma migrate dev --name init -``` + ```npm + npx prisma migrate dev --name init + ``` Then generate Prisma Client: -```npm -npx prisma generate -``` + ```npm + npx prisma generate + ``` Now, before moving on, let's update your `db.server.ts` file to use the newly generated Prisma client with the driver adapter: diff --git a/apps/docs/content/docs/guides/making-guides.mdx b/apps/docs/content/docs/guides/making-guides.mdx index 9242c5244e..21d0690c5e 100644 --- a/apps/docs/content/docs/guides/making-guides.mdx +++ b/apps/docs/content/docs/guides/making-guides.mdx @@ -1,9 +1,9 @@ --- title: Writing guides -description: "Learn how to write clear, consistent, and helpful guides for Prisma documentation" +description: 'Learn how to write clear, consistent, and helpful guides for Prisma documentation' url: /guides/making-guides metaTitle: How to write guides for Prisma ORM -metaDescription: "Learn how to write clear, consistent, and helpful guides for Prisma ORM documentation" +metaDescription: 'Learn how to write clear, consistent, and helpful guides for Prisma ORM documentation' --- ## Introduction @@ -27,8 +27,8 @@ Every guide must include the following frontmatter at the top of the file: ```mdx --- -title: "[Descriptive title]" -description: "[One-sentence summary of what the guide covers]" +title: '[Descriptive title]' +description: '[One-sentence summary of what the guide covers]' --- ``` @@ -127,7 +127,6 @@ export default defineConfig({ - Code elements: `` `PrismaClient` `` - Package manager commands: Use ` ```npm ` blocks (see [Package manager commands](#package-manager-commands)) - Use admonitions for important information: - ```markdown :::info Context or background information @@ -145,22 +144,21 @@ export default defineConfig({ Helpful suggestions or best practices ::: ``` - - Use proper heading hierarchy (never skip levels) - Use numbered sections (e.g., "## 1. Setup", "### 1.1. Install") - Link to other documentation pages using relative paths (e.g., `[Database drivers](/orm/core-concepts/supported-databases/database-drivers)`) ## Guide categories -| Category | Directory | Description | Examples | -| ------------------- | ------------------------------ | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Framework** | `guides/frameworks/` | Integrate Prisma with frameworks | [Next.js](/guides/frameworks/nextjs), [NestJS](/guides/frameworks/nestjs), [SvelteKit](/guides/frameworks/sveltekit) | -| **Deployment** | `guides/deployment/` | Deploy apps and set up monorepos | [Turborepo](/guides/deployment/turborepo), [Cloudflare Workers](/guides/deployment/cloudflare-workers) | -| **Integration** | `guides/integrations/` | Use Prisma with platforms and tools | [GitHub Actions](/guides/integrations/github-actions), [Supabase](/guides/integrations/supabase-accelerate) | -| **Database** | `guides/database/` | Database patterns and migrations | [Multiple databases](/guides/database/multiple-databases), [Data migration](/guides/database/data-migration) | -| **Authentication** | `guides/authentication/` | Authentication patterns with Prisma | [Auth.js + Next.js](/guides/authentication/authjs/nextjs), [Better Auth + Next.js](/guides/authentication/better-auth/nextjs), [Clerk + Next.js](/guides/authentication/clerk/nextjs) | -| **Prisma Postgres** | `guides/postgres/` | Prisma Postgres features | [Vercel](/guides/postgres/vercel), [Netlify](/guides/postgres/netlify), [Viewing data](/guides/postgres/viewing-data) | -| **Migration** | `guides/switch-to-prisma-orm/` | Switch from other ORMs | [From Mongoose](/guides/switch-to-prisma-orm/from-mongoose), [From Drizzle](/guides/switch-to-prisma-orm/from-drizzle) | +| Category | Directory | Description | Examples | +|----------|-----------|-------------|----------| +| **Framework** | `guides/frameworks/` | Integrate Prisma with frameworks | [Next.js](/guides/frameworks/nextjs), [NestJS](/guides/frameworks/nestjs), [SvelteKit](/guides/frameworks/sveltekit) | +| **Deployment** | `guides/deployment/` | Deploy apps and set up monorepos | [Turborepo](/guides/deployment/turborepo), [Cloudflare Workers](/guides/deployment/cloudflare-workers) | +| **Integration** | `guides/integrations/` | Use Prisma with platforms and tools | [GitHub Actions](/guides/integrations/github-actions), [Supabase](/guides/integrations/supabase-accelerate) | +| **Database** | `guides/database/` | Database patterns and migrations | [Multiple databases](/guides/database/multiple-databases), [Data migration](/guides/database/data-migration) | +| **Authentication** | `guides/authentication/` | Authentication patterns with Prisma | [Auth.js + Next.js](/guides/authentication/authjs/nextjs), [Better Auth + Next.js](/guides/authentication/better-auth/nextjs), [Clerk + Next.js](/guides/authentication/clerk/nextjs) | +| **Prisma Postgres** | `guides/postgres/` | Prisma Postgres features | [Vercel](/guides/postgres/vercel), [Netlify](/guides/postgres/netlify), [Viewing data](/guides/postgres/viewing-data) | +| **Migration** | `guides/switch-to-prisma-orm/` | Switch from other ORMs | [From Mongoose](/guides/switch-to-prisma-orm/from-mongoose), [From Drizzle](/guides/switch-to-prisma-orm/from-drizzle) | ## Common patterns diff --git a/apps/docs/content/docs/guides/switch-to-prisma-orm/from-sql-orms.mdx b/apps/docs/content/docs/guides/switch-to-prisma-orm/from-sql-orms.mdx index f1a1b9200e..a5efb61768 100644 --- a/apps/docs/content/docs/guides/switch-to-prisma-orm/from-sql-orms.mdx +++ b/apps/docs/content/docs/guides/switch-to-prisma-orm/from-sql-orms.mdx @@ -11,7 +11,6 @@ metaDescription: Learn how to migrate from TypeORM to Prisma ORM This guide shows you how to migrate your application from Sequelize or TypeORM to Prisma ORM. You can learn how Prisma ORM compares to these ORMs on the comparison pages: - - [Prisma ORM vs Sequelize](/orm/more/comparisons/prisma-and-sequelize) - [Prisma ORM vs TypeORM](/orm/more/comparisons/prisma-and-typeorm) @@ -92,17 +91,20 @@ const user = await User.findOne({ where: { id: 1 } }); const users = await User.findAll({ where: { active: true }, limit: 10, - order: [["createdAt", "DESC"]], + order: [['createdAt', 'DESC']] }); // Create const user = await User.create({ - name: "Alice", - email: "alice@example.com", + name: 'Alice', + email: 'alice@example.com' }); // Update -await User.update({ name: "Alicia" }, { where: { id: 1 } }); +await User.update( + { name: 'Alicia' }, + { where: { id: 1 } } +); // Delete await User.destroy({ where: { id: 1 } }); @@ -110,13 +112,13 @@ await User.destroy({ where: { id: 1 } }); // With relations const posts = await Post.findAll({ include: [{ model: User }], - limit: 10, + limit: 10 }); // Transaction const result = await sequelize.transaction(async (t) => { - const user = await User.create({ name: "Alice" }, { transaction: t }); - const post = await Post.create({ title: "Hello", userId: user.id }, { transaction: t }); + const user = await User.create({ name: 'Alice' }, { transaction: t }); + const post = await Post.create({ title: 'Hello', userId: user.id }, { transaction: t }); return { user, post }; }); ``` @@ -127,33 +129,33 @@ const user = await userRepository.findOne({ where: { id: 1 } }); const users = await userRepository.find({ where: { active: true }, take: 10, - order: { createdAt: "DESC" }, + order: { createdAt: 'DESC' } }); // Create const user = userRepository.create({ - name: "Alice", - email: "alice@example.com", + name: 'Alice', + email: 'alice@example.com' }); await userRepository.save(user); // Update -await userRepository.update(1, { name: "Alicia" }); +await userRepository.update(1, { name: 'Alicia' }); // Delete await userRepository.delete(1); // With relations const posts = await postRepository.find({ - relations: ["author"], - take: 10, + relations: ['author'], + take: 10 }); // Transaction await connection.transaction(async (manager) => { - const user = manager.create(User, { name: "Alice" }); + const user = manager.create(User, { name: 'Alice' }); await manager.save(user); - const post = manager.create(Post, { title: "Hello", author: user }); + const post = manager.create(Post, { title: 'Hello', author: user }); await manager.save(post); }); ``` @@ -164,21 +166,21 @@ const user = await prisma.user.findUnique({ where: { id: 1 } }); const users = await prisma.user.findMany({ where: { active: true }, take: 10, - orderBy: { createdAt: "desc" }, + orderBy: { createdAt: 'desc' } }); // Create const user = await prisma.user.create({ data: { - name: "Alice", - email: "alice@example.com", - }, + name: 'Alice', + email: 'alice@example.com' + } }); // Update await prisma.user.update({ where: { id: 1 }, - data: { name: "Alicia" }, + data: { name: 'Alicia' } }); // Delete @@ -187,17 +189,17 @@ await prisma.user.delete({ where: { id: 1 } }); // With relations const posts = await prisma.post.findMany({ include: { author: true }, - take: 10, + take: 10 }); // Transaction const result = await prisma.$transaction(async (tx) => { const user = await tx.user.create({ data: { - name: "Alice", - posts: { create: { title: "Hello" } }, + name: 'Alice', + posts: { create: { title: 'Hello' } } }, - include: { posts: true }, + include: { posts: true } }); return user; }); diff --git a/apps/docs/content/docs/guides/upgrade-prisma-orm/v1.mdx b/apps/docs/content/docs/guides/upgrade-prisma-orm/v1.mdx index 94df80ee17..6bdf8d2511 100644 --- a/apps/docs/content/docs/guides/upgrade-prisma-orm/v1.mdx +++ b/apps/docs/content/docs/guides/upgrade-prisma-orm/v1.mdx @@ -2,8 +2,9 @@ title: Upgrade to v1 description: Comprehensive guide for upgrading from Prisma 1 to Prisma ORM v1 url: /guides/upgrade-prisma-orm/v1 -metaTitle: "Upgrade from Prisma 1 to Prisma ORM 2" -metaDescription: "Upgrading your project from Prisma 1 to Prisma ORM 2" +metaTitle: 'Upgrade from Prisma 1 to Prisma ORM 2' +metaDescription: 'Upgrading your project from Prisma 1 to Prisma ORM 2' + --- This guide provides a comprehensive roadmap for migrating your project from Prisma 1 to the latest version of Prisma ORM. The migration process involves significant architectural changes and requires careful planning and execution. @@ -19,13 +20,13 @@ This guide provides a comprehensive roadmap for migrating your project from Pris ### Architectural changes -| Feature | Prisma 1 | Prisma ORM | -| ----------------------- | ----------------------------- | ----------------------------------------- | -| **Database Connection** | Uses Prisma Server as a proxy | Direct database connection | -| **API** | GraphQL API for database | Programmatic access via Prisma Client | -| **Schema** | GraphQL SDL + `prisma.yml` | Unified Prisma schema | -| **Modeling** | GraphQL SDL | Prisma Schema Language (PSL) | -| **Workflow** | `prisma deploy` | `prisma migrate` and `prisma db` commands | +| Feature | Prisma 1 | Prisma ORM | +|---------|----------|------------| +| **Database Connection** | Uses Prisma Server as a proxy | Direct database connection | +| **API** | GraphQL API for database | Programmatic access via Prisma Client | +| **Schema** | GraphQL SDL + `prisma.yml` | Unified Prisma schema | +| **Modeling** | GraphQL SDL | Prisma Schema Language (PSL) | +| **Workflow** | `prisma deploy` | `prisma migrate` and `prisma db` commands | ### Feature changes @@ -107,12 +108,12 @@ model Post { #### 3.1 Handling Special Types -| Prisma 1 Type | Prisma ORM Equivalent | Notes | -| ------------- | ----------------------------- | -------------------------- | -| `ID` | `String @id @default(cuid())` | Add `@id` directive | -| `DateTime` | `DateTime` | No change needed | -| `Json` | `Json` | No change needed | -| `Enum` | `Enum` | Define enums in the schema | +| Prisma 1 Type | Prisma ORM Equivalent | Notes | +|---------------|----------------------|-------| +| `ID` | `String @id @default(cuid())` | Add `@id` directive | +| `DateTime` | `DateTime` | No change needed | +| `Json` | `Json` | No change needed | +| `Enum` | `Enum` | Define enums in the schema | #### 3.2 Relation Handling @@ -138,19 +139,19 @@ model Post { ```typescript // Before (Prisma 1) -import { prisma } from "./generated/prisma-client"; +import { prisma } from './generated/prisma-client'; async function getUser(id: string) { return prisma.user({ id }); } // After (Prisma ORM) -import { PrismaClient } from "@prisma/client"; +import { PrismaClient } from '@prisma/client'; const prisma = new PrismaClient(); async function getUser(id: number) { return prisma.user.findUnique({ - where: { id }, + where: { id } }); } ``` @@ -167,7 +168,7 @@ const posts = await prisma.user({ id: 1 }).posts(); // After (Prisma ORM) const user = await prisma.user.findUnique({ where: { id: 1 }, - include: { posts: true }, + include: { posts: true } }); const posts = user?.posts; ``` @@ -177,16 +178,16 @@ const posts = user?.posts; ```typescript // Before (Prisma 1) const newUser = await prisma.createUser({ - name: "Alice", - email: "alice@example.com", + name: 'Alice', + email: 'alice@example.com' }); // After (Prisma ORM) const newUser = await prisma.user.create({ data: { - name: "Alice", - email: "alice@example.com", - }, + name: 'Alice', + email: 'alice@example.com' + } }); ``` @@ -199,23 +200,23 @@ Test all CRUD operations to ensure data consistency: ```typescript // Test create const user = await prisma.user.create({ - data: { name: "Test", email: "test@example.com" }, + data: { name: 'Test', email: 'test@example.com' } }); // Test read const foundUser = await prisma.user.findUnique({ - where: { id: user.id }, + where: { id: user.id } }); // Test update const updatedUser = await prisma.user.update({ where: { id: user.id }, - data: { name: "Updated Name" }, + data: { name: 'Updated Name' } }); // Test delete await prisma.user.delete({ - where: { id: user.id }, + where: { id: user.id } }); ``` @@ -229,25 +230,25 @@ const userWithPosts = await prisma.user.findUnique({ where: { id: 1 }, include: { posts: true, - profile: true, - }, + profile: true + } }); // Test nested writes const userWithNewPost = await prisma.user.create({ data: { - name: "Bob", - email: "bob@example.com", + name: 'Bob', + email: 'bob@example.com', posts: { create: { - title: "Hello World", - content: "This is my first post", - }, - }, + title: 'Hello World', + content: 'This is my first post' + } + } }, include: { - posts: true, - }, + posts: true + } }); ``` @@ -280,14 +281,14 @@ FOR EACH ROW EXECUTE FUNCTION notify_new_post(); // Publish event when creating a post const post = await prisma.post.create({ data: { - title: "New Post", - content: "Content", - author: { connect: { id: userId } }, - }, + title: 'New Post', + content: 'Content', + author: { connect: { id: userId }} + } }); // Publish event to your pub/sub system -await pubsub.publish("POST_CREATED", { postCreated: post }); +await pubsub.publish('POST_CREATED', { postCreated: post }); ``` ### 2. Authentication @@ -295,16 +296,16 @@ await pubsub.publish("POST_CREATED", { postCreated: post }); If you were using Prisma 1's built-in authentication, you'll need to implement your own solution: ```typescript -import { compare } from "bcryptjs"; -import { sign } from "jsonwebtoken"; +import { compare } from 'bcryptjs'; +import { sign } from 'jsonwebtoken'; export async function login(email: string, password: string) { const user = await prisma.user.findUnique({ where: { email } }); - if (!user) throw new Error("User not found"); - + if (!user) throw new Error('User not found'); + const valid = await compare(password, user.password); - if (!valid) throw new Error("Invalid password"); - + if (!valid) throw new Error('Invalid password'); + const token = sign({ userId: user.id }, process.env.APP_SECRET!); return { token, user }; } @@ -325,7 +326,6 @@ prisma1-upgrade ``` This tool helps with: - - Converting your Prisma 1 datamodel to Prisma schema - Identifying potential issues in your schema - Providing migration recommendations @@ -333,15 +333,14 @@ This tool helps with: ## Performance Considerations 1. **Connection Pooling**: Configure connection pooling for better performance: - ```typescript const prisma = new PrismaClient({ - log: ["query", "info", "warn", "error"], + log: ['query', 'info', 'warn', 'error'], datasources: { db: { - url: process.env.DATABASE_URL + "&connection_limit=20", - }, - }, + url: process.env.DATABASE_URL + '&connection_limit=20' + } + } }); ``` @@ -352,8 +351,8 @@ This tool helps with: select: { id: true, name: true, - email: true, - }, + email: true + } }); ``` @@ -368,7 +367,6 @@ This tool helps with: ## Getting Help If you encounter issues during migration: - 1. Search the [GitHub Issues](https://github.com/prisma/prisma/issues) 2. Ask for help in the [Prisma Slack](https://slack.prisma.io/) 3. Open a [GitHub Discussion](https://github.com/prisma/prisma/discussions) diff --git a/apps/docs/content/docs/guides/upgrade-prisma-orm/v3.mdx b/apps/docs/content/docs/guides/upgrade-prisma-orm/v3.mdx index 7cfc39cdc9..37e11d9b9c 100644 --- a/apps/docs/content/docs/guides/upgrade-prisma-orm/v3.mdx +++ b/apps/docs/content/docs/guides/upgrade-prisma-orm/v3.mdx @@ -2,8 +2,9 @@ title: Upgrade to v3 description: Comprehensive guide for upgrading to Prisma ORM v3 url: /guides/upgrade-prisma-orm/v3 -metaTitle: "Upgrade to Prisma ORM 3" -metaDescription: "Guides on how to upgrade to Prisma ORM 3" +metaTitle: 'Upgrade to Prisma ORM 3' +metaDescription: 'Guides on how to upgrade to Prisma ORM 3' + --- This guide helps you upgrade your project to Prisma ORM v3. Version 3 includes several important changes that may affect your application, so please review this guide carefully before proceeding. @@ -50,8 +51,8 @@ model User { model Post { id Int @id @default(autoincrement()) author User @relation( - fields: [authorId], - references: [id], + fields: [authorId], + references: [id], onDelete: Cascade, // Explicit action required onUpdate: Cascade // Optional: define update behavior ) @@ -108,7 +109,7 @@ The `$queryRaw` method now only supports template literals for security: ```typescript // String syntax (no longer supported in v3) -const result = await prisma.$queryRaw("SELECT * FROM User WHERE id = 1"); +const result = await prisma.$queryRaw('SELECT * FROM User WHERE id = 1'); ``` #### After (Prisma 3.x): @@ -119,7 +120,10 @@ const userId = 1; const result = await prisma.$queryRaw`SELECT * FROM User WHERE id = ${userId}`; // Or use $queryRawUnsafe (use with caution) -const unsafeResult = await prisma.$queryRawUnsafe("SELECT * FROM User WHERE id = $1", userId); +const unsafeResult = await prisma.$queryRawUnsafe( + 'SELECT * FROM User WHERE id = $1', + userId +); ``` **Action Required**: Update all `$queryRaw` calls in your codebase to use template literals or switch to `$queryRawUnsafe` with proper parameterization. @@ -141,49 +145,49 @@ Prisma ORM 3 introduces more precise handling of null values in JSON fields: const result = await prisma.log.findMany({ where: { meta: { - equals: null, // Could mean either JSON null or database NULL - }, - }, + equals: null // Could mean either JSON null or database NULL + } + } }); ``` #### After (Prisma 3.x): ```typescript -import { Prisma } from "@prisma/client"; +import { Prisma } from '@prisma/client'; // Check for JSON null const jsonNullResults = await prisma.log.findMany({ where: { meta: { - equals: Prisma.JsonNull, - }, - }, + equals: Prisma.JsonNull + } + } }); // Check for database NULL const dbNullResults = await prisma.log.findMany({ where: { meta: { - equals: Prisma.DbNull, - }, - }, + equals: Prisma.DbNull + } + } }); // Check for either const anyNullResults = await prisma.log.findMany({ where: { meta: { - equals: Prisma.AnyNull, - }, - }, + equals: Prisma.AnyNull + } + } }); // Creating with specific null types await prisma.log.create({ data: { - meta: Prisma.JsonNull, // or Prisma.DbNull - }, + meta: Prisma.JsonNull // or Prisma.DbNull + } }); ``` diff --git a/apps/docs/content/docs/guides/upgrade-prisma-orm/v4.mdx b/apps/docs/content/docs/guides/upgrade-prisma-orm/v4.mdx index 4b8a5f13dd..2035e9ad0e 100644 --- a/apps/docs/content/docs/guides/upgrade-prisma-orm/v4.mdx +++ b/apps/docs/content/docs/guides/upgrade-prisma-orm/v4.mdx @@ -2,8 +2,9 @@ title: Upgrade to v4 description: Comprehensive guide for upgrading to Prisma ORM v4 url: /guides/upgrade-prisma-orm/v4 -metaTitle: "Upgrade to Prisma ORM 4" -metaDescription: "Guides on how to upgrade to Prisma ORM 4" +metaTitle: 'Upgrade to Prisma ORM 4' +metaDescription: 'Guides on how to upgrade to Prisma ORM 4' + --- This guide helps you upgrade your project to Prisma ORM 4. Version 4 includes several important changes that may affect your application, so please review this guide carefully before proceeding. @@ -47,6 +48,7 @@ npm install -D prisma@4 npx prisma generate ``` + :::danger Before you upgrade, check each breaking change below to see how the upgrade might affect your application. @@ -139,12 +141,12 @@ This will update your schema with new capabilities like improved index configura In Prisma 4, the return types of `queryRaw` and `queryRawUnsafe` have changed: -| Data Type | Before 4.0.0 | From 4.0.0 | -| ---------- | ------------ | ---------- | -| `DateTime` | `String` | `Date` | -| `BigInt` | `String` | `BigInt` | -| `Bytes` | `String` | `Buffer` | -| `Decimal` | `String` | `Decimal` | +| Data Type | Before 4.0.0 | From 4.0.0 | +|------------|---------------|---------------| +| `DateTime` | `String` | `Date` | +| `BigInt` | `String` | `BigInt` | +| `Bytes` | `String` | `Buffer` | +| `Decimal` | `String` | `Decimal` | **Update your code** to handle these type changes, especially if you're using TypeScript or performing type checks. @@ -156,17 +158,17 @@ Prisma 4 changes how JSON null values are handled. If you're working with JSON f // Before const result = await prisma.model.findMany({ where: { - jsonField: { equals: null }, - }, + jsonField: { equals: null } + } }); // After -import { Prisma } from "@prisma/client"; +import { Prisma } from '@prisma/client'; const result = await prisma.model.findMany({ where: { - jsonField: { equals: Prisma.JsonNull }, - }, + jsonField: { equals: Prisma.JsonNull } + } }); ``` @@ -231,7 +233,6 @@ model User { ### 3. Better Type Safety Prisma 4 improves type safety with: - - Stricter validation of relation fields - Better error messages for common mistakes - Improved type inference for complex queries @@ -287,7 +288,6 @@ Prisma 4 improves type safety with: ## Getting Help If you encounter issues during the upgrade: - 1. Check the [GitHub Issues](https://github.com/prisma/prisma/issues) for known problems 2. Ask for help in the [Prisma Slack](https://slack.prisma.io/) 3. Open a [GitHub Discussion](https://github.com/prisma/prisma/discussions) diff --git a/apps/docs/content/docs/guides/upgrade-prisma-orm/v5.mdx b/apps/docs/content/docs/guides/upgrade-prisma-orm/v5.mdx index c1bdb580cd..7a9964e058 100644 --- a/apps/docs/content/docs/guides/upgrade-prisma-orm/v5.mdx +++ b/apps/docs/content/docs/guides/upgrade-prisma-orm/v5.mdx @@ -2,8 +2,9 @@ title: Upgrade to v5 description: Comprehensive guide for upgrading to Prisma ORM v5 url: /guides/upgrade-prisma-orm/v5 -metaTitle: "Upgrade to Prisma ORM 5" -metaDescription: "Guides on how to upgrade to Prisma ORM 5" +metaTitle: 'Upgrade to Prisma ORM 5' +metaDescription: 'Guides on how to upgrade to Prisma ORM 5' + --- Prisma ORM v5.0.0 introduces a number of changes, including the usage of our new JSON Protocol, [which makes Prisma Client faster by default](https://www.prisma.io/blog/prisma-5-f66prwkjx72s). A full list of these changes can be found [in our release notes](https://github.com/prisma/prisma/releases/tag/5.0.0). @@ -14,6 +15,7 @@ This guide explains how upgrading might affect your application and gives instru To upgrade to Prisma ORM 5 from an earlier version, you need to update both the `prisma` and `@prisma/client` packages. + ```npm npm install @prisma/client@5 npm install -D prisma@5 diff --git a/apps/docs/content/docs/guides/upgrade-prisma-orm/v6.mdx b/apps/docs/content/docs/guides/upgrade-prisma-orm/v6.mdx index fa29d7ca0a..01c436ac52 100644 --- a/apps/docs/content/docs/guides/upgrade-prisma-orm/v6.mdx +++ b/apps/docs/content/docs/guides/upgrade-prisma-orm/v6.mdx @@ -2,8 +2,9 @@ title: Upgrade to v6 description: Comprehensive guide for upgrading to Prisma ORM v6 url: /guides/upgrade-prisma-orm/v6 -metaTitle: "Upgrade to Prisma ORM 6" -metaDescription: "Guides on how to upgrade to Prisma ORM 6" +metaTitle: 'Upgrade to Prisma ORM 6' +metaDescription: 'Guides on how to upgrade to Prisma ORM 6' + --- Prisma ORM v6 introduces a number of **breaking changes** when you upgrade from an earlier Prisma ORM version. This guide explains how this upgrade might affect your application and gives instructions on how to handle any changes. @@ -97,7 +98,7 @@ CREATE TABLE "_PostToTag" ( ); -- CreateIndex -CREATE UNIQUE INDEX "_PostToTag_AB_unique" ON "_PostToTag"("A", "B"); +CREATE UNIQUE INDEX "_PostToTag_AB_unique" ON "_PostToTag"("A", "B"); -- CreateIndex CREATE INDEX "_PostToTag_B_index" ON "_PostToTag"("B"); @@ -117,7 +118,7 @@ CREATE TABLE "_PostToTag" ( "A" INTEGER NOT NULL, "B" INTEGER NOT NULL, - CONSTRAINT "_PostToTag_AB_pkey" PRIMARY KEY ("A","B") + CONSTRAINT "_PostToTag_AB_pkey" PRIMARY KEY ("A","B") ); -- CreateIndex @@ -164,7 +165,7 @@ datasource db { generator client { provider = "prisma-client-js" - previewFeatures = ["fullTextSearch"] + previewFeatures = ["fullTextSearch"] } ``` @@ -178,7 +179,7 @@ datasource db { generator client { provider = "prisma-client-js" - previewFeatures = ["fullTextSearchPostgres"] + previewFeatures = ["fullTextSearchPostgres"] } ``` diff --git a/apps/docs/content/docs/guides/upgrade-prisma-orm/v7.mdx b/apps/docs/content/docs/guides/upgrade-prisma-orm/v7.mdx index 8322064f3a..fab5145120 100644 --- a/apps/docs/content/docs/guides/upgrade-prisma-orm/v7.mdx +++ b/apps/docs/content/docs/guides/upgrade-prisma-orm/v7.mdx @@ -2,8 +2,9 @@ title: Upgrade to v7 description: Comprehensive guide for upgrading to Prisma ORM v7 url: /guides/upgrade-prisma-orm/v7 -metaTitle: "Upgrade to Prisma ORM 7" -metaDescription: "Guide on how to upgrade to Prisma ORM 7" +metaTitle: 'Upgrade to Prisma ORM 7' +metaDescription: 'Guide on how to upgrade to Prisma ORM 7' + --- Prisma ORM v7 introduces **breaking changes** when you upgrade from an earlier Prisma ORM version. This guide explains how this upgrade might affect your application and gives instructions on how to handle any changes. @@ -30,6 +31,7 @@ If you are using MongoDB, please note that Prisma ORM v7 does not yet support Mo To upgrade to Prisma ORM v7 from an earlier version, you need to update both the `prisma` and `@prisma/client` packages: + ```npm npm install @prisma/client@7 npm install -D prisma@7 @@ -84,6 +86,7 @@ queries, smaller bundle size, and require less system resources when deployed to Additionally, the `output` field is now **required** in the generator block. Prisma Client will no longer be generated in `node_modules` by default. You must specify a custom output path. + ```prisma tab="Before" generator client { provider = "prisma-client-js" @@ -91,6 +94,7 @@ generator client { } ``` + ```prisma tab="After" generator client { provider = "prisma-client" diff --git a/apps/docs/content/docs/management-api/api-clients.mdx b/apps/docs/content/docs/management-api/api-clients.mdx index 96e8496238..923bb7df21 100644 --- a/apps/docs/content/docs/management-api/api-clients.mdx +++ b/apps/docs/content/docs/management-api/api-clients.mdx @@ -1,6 +1,6 @@ --- title: Using API Clients -description: "Use the Management API with popular API clients like Postman, Insomnia, and Yaak" +description: 'Use the Management API with popular API clients like Postman, Insomnia, and Yaak' url: /management-api/api-clients metaTitle: How to use the Management API with API Clients metaDescription: Learn how to use the Management API with API Clients @@ -196,15 +196,15 @@ Now you'll set up authentication in Yaak: 5. Set the authentication type to **OAuth 2.0** 6. Enter the following values: -| Parameter | Value | -| ----------------- | ----------------------------------- | -| Grant Type | Authorization Code | -| Authorization URL | `https://auth.prisma.io/authorize` | -| Token URL | `https://auth.prisma.io/token` | -| Client ID | `your-client-id` | -| Client Secret | `your-client-secret` | -| Redirect URL | `https://devnull.yaak.app/callback` | -| Scope | `workspace:admin` | +| Parameter | Value | +| ------------------ | -------------------------------------- | +| Grant Type | Authorization Code | +| Authorization URL | `https://auth.prisma.io/authorize` | +| Token URL | `https://auth.prisma.io/token` | +| Client ID | `your-client-id` | +| Client Secret | `your-client-secret` | +| Redirect URL | `https://devnull.yaak.app/callback` | +| Scope | `workspace:admin` | 7. Click **Get Token** 8. A browser window will open and have you complete the authorization flow diff --git a/apps/docs/content/docs/management-api/authentication.mdx b/apps/docs/content/docs/management-api/authentication.mdx index 53c6daba1a..17e15cce48 100644 --- a/apps/docs/content/docs/management-api/authentication.mdx +++ b/apps/docs/content/docs/management-api/authentication.mdx @@ -2,7 +2,7 @@ title: Authentication description: Learn how to authenticate with the Prisma Management API using service tokens or OAuth 2.0 metaTitle: Management API Authentication | Service Tokens & OAuth 2.0 -metaDescription: "Authenticate with Prisma Management API: service tokens for scripts and CI/CD, or OAuth 2.0 with PKCE for user-facing apps. Bearer token in Authorization header." +metaDescription: 'Authenticate with Prisma Management API: service tokens for scripts and CI/CD, or OAuth 2.0 with PKCE for user-facing apps. Bearer token in Authorization header.' url: /management-api/authentication --- @@ -76,7 +76,6 @@ This provides enhanced security, especially for mobile and single-page applicati :::note[Development redirect URIs] For local development, the following redirect URIs are accepted with any port via wildcard matching: - - `localhost` (e.g., `http://localhost:3000/callback`) - `127.0.0.1` (e.g., `http://127.0.0.1:3000/callback`) - `[::1]` - IPv6 loopback (e.g., `http://[::1]:3000/callback`) @@ -85,11 +84,11 @@ For local development, the following redirect URIs are accepted with any port vi ### OAuth Endpoints -| Endpoint | URL | -| ------------- | --------------------------------------------------------------- | -| Authorization | `https://auth.prisma.io/authorize` | -| Token | `https://auth.prisma.io/token` | -| Discovery | `https://auth.prisma.io/.well-known/oauth-authorization-server` | +| Endpoint | URL | +|----------|-----| +| Authorization | `https://auth.prisma.io/authorize` | +| Token | `https://auth.prisma.io/token` | +| Discovery | `https://auth.prisma.io/.well-known/oauth-authorization-server` | :::tip @@ -99,17 +98,17 @@ The discovery endpoint provides OAuth server metadata that can be used for autom ### Available Scopes -| Scope | Description | -| ----------------- | ---------------------------------------------- | -| `workspace:admin` | Full access to workspace resources | -| `offline_access` | Enables refresh tokens for long-lived sessions | +| Scope | Description | +|-------|-------------| +| `workspace:admin` | Full access to workspace resources | +| `offline_access` | Enables refresh tokens for long-lived sessions | ### Token Lifetimes -| Token Type | Expiration | -| -------------- | ---------- | -| Access tokens | 1 hour | -| Refresh tokens | 90 days | +| Token Type | Expiration | +|-----------|------------| +| Access tokens | 1 hour | +| Refresh tokens | 90 days | ### OAuth Authorization Flow @@ -117,12 +116,12 @@ The discovery endpoint provides OAuth server metadata that can be used for autom Redirect users to the authorization endpoint with the following query parameters: -| Parameter | Description | -| --------------- | ------------------------------------------------------------------- | -| `client_id` | Your OAuth application's Client ID | -| `redirect_uri` | The callback URL where users will be redirected after authorization | -| `response_type` | Must be `code` for the authorization code flow | -| `scope` | Permissions to request (e.g., `workspace:admin`) | +| Parameter | Description | +|-----------|-------------| +| `client_id` | Your OAuth application's Client ID | +| `redirect_uri` | The callback URL where users will be redirected after authorization | +| `response_type` | Must be `code` for the authorization code flow | +| `scope` | Permissions to request (e.g., `workspace:admin`) | ``` https://auth.prisma.io/authorize?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&response_type=code&scope=workspace:admin diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/delete-compute-services-by-compute-service-id.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/delete-compute-services-by-compute-service-id.mdx index b1ca93c4a7..7ab940e39c 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/delete-compute-services-by-compute-service-id.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/delete-compute-services-by-compute-service-id.mdx @@ -1,6 +1,6 @@ --- title: Delete compute service -description: "Experimental endpoint: this API is in active development and may change at any time without notice. Deletes a compute service. All compute versions under the service must already be stopped or deleted." +description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Deletes a compute service. All compute versions under the service must already be stopped or deleted.' full: true _openapi: method: DELETE @@ -12,10 +12,10 @@ _openapi: ⚠️ Experimental endpoint: this API is in active development and may change at any time without notice. ⚠️ Deletes a compute service. All compute versions under the service must already be stopped or deleted. - path: "/v1/compute-services/{computeServiceId}" -url: "/management-api/endpoints/[experimental]/delete-compute-services-by-compute-service-id" -metaTitle: "DELETE /v1/compute-services/{computeServiceId} | Delete compute service" -metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Deletes a compute service. All compute versions under the service must already be stopped or deleted." + path: '/v1/compute-services/{computeServiceId}' +url: '/management-api/endpoints/[experimental]/delete-compute-services-by-compute-service-id' +metaTitle: 'DELETE /v1/compute-services/{computeServiceId} | Delete compute service' +metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Deletes a compute service. All compute versions under the service must already be stopped or deleted.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,7 +24,4 @@ metaDescription: "Management API: Experimental endpoint: this API is in active d Deletes a compute service. All compute versions under the service must already be stopped or deleted. - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/delete-compute-services-versions-by-version-id.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/delete-compute-services-versions-by-version-id.mdx index 917dd5cf6e..5b09919281 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/delete-compute-services-versions-by-version-id.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/delete-compute-services-versions-by-version-id.mdx @@ -1,6 +1,6 @@ --- title: Delete compute version -description: "Experimental endpoint: this API is in active development and may change at any time without notice. Permanently deletes the compute version, its metadata, and any associated VM. The version must be stopped or in the `new` state before it can be deleted." +description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Permanently deletes the compute version, its metadata, and any associated VM. The version must be stopped or in the `new` state before it can be deleted.' full: true _openapi: method: DELETE @@ -12,10 +12,10 @@ _openapi: ⚠️ Experimental endpoint: this API is in active development and may change at any time without notice. ⚠️ Permanently deletes the compute version, its metadata, and any associated VM. The version must be stopped or in the `new` state before it can be deleted. - path: "/v1/compute-services/versions/{versionId}" -url: "/management-api/endpoints/[experimental]/delete-compute-services-versions-by-version-id" -metaTitle: "DELETE /v1/compute-services/versions/{versionId} | Delete compute version" -metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Permanently deletes the compute version, its metadata, and any associated VM. The version must be stopped or in the `new` state before it can be deleted." + path: '/v1/compute-services/versions/{versionId}' +url: '/management-api/endpoints/[experimental]/delete-compute-services-versions-by-version-id' +metaTitle: 'DELETE /v1/compute-services/versions/{versionId} | Delete compute version' +metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Permanently deletes the compute version, its metadata, and any associated VM. The version must be stopped or in the `new` state before it can be deleted.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,7 +24,4 @@ metaDescription: "Management API: Experimental endpoint: this API is in active d Permanently deletes the compute version, its metadata, and any associated VM. The version must be stopped or in the `new` state before it can be deleted. - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/delete-versions-by-version-id.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/delete-versions-by-version-id.mdx index 0dae533b3e..331fe8dd45 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/delete-versions-by-version-id.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/delete-versions-by-version-id.mdx @@ -1,6 +1,6 @@ --- title: Delete compute version -description: "Experimental endpoint: this API is in active development and may change at any time without notice. Permanently deletes the compute version, its metadata, and any associated VM. The version must be stopped or in the `new` state before it can be deleted." +description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Permanently deletes the compute version, its metadata, and any associated VM. The version must be stopped or in the `new` state before it can be deleted.' full: true _openapi: method: DELETE @@ -12,10 +12,10 @@ _openapi: ⚠️ Experimental endpoint: this API is in active development and may change at any time without notice. ⚠️ Permanently deletes the compute version, its metadata, and any associated VM. The version must be stopped or in the `new` state before it can be deleted. - path: "/v1/versions/{versionId}" -url: "/management-api/endpoints/[experimental]/delete-versions-by-version-id" -metaTitle: "DELETE /v1/versions/{versionId} | Delete compute version" -metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Permanently deletes the compute version, its metadata, and any associated VM. The version must be stopped or in the `new` state before it can be deleted." + path: '/v1/versions/{versionId}' +url: '/management-api/endpoints/[experimental]/delete-versions-by-version-id' +metaTitle: 'DELETE /v1/versions/{versionId} | Delete compute version' +metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Permanently deletes the compute version, its metadata, and any associated VM. The version must be stopped or in the `new` state before it can be deleted.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,7 +24,4 @@ metaDescription: "Management API: Experimental endpoint: this API is in active d Permanently deletes the compute version, its metadata, and any associated VM. The version must be stopped or in the `new` state before it can be deleted. - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services-by-compute-service-id-versions.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services-by-compute-service-id-versions.mdx index 8203b67e95..a1bae85dc5 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services-by-compute-service-id-versions.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services-by-compute-service-id-versions.mdx @@ -1,6 +1,6 @@ --- title: List compute versions -description: "Experimental endpoint: this API is in active development and may change at any time without notice. Returns all compute versions belonging to a compute service, ordered by creation time (newest first). Supports cursor-based pagination." +description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Returns all compute versions belonging to a compute service, ordered by creation time (newest first). Supports cursor-based pagination.' full: true _openapi: method: GET @@ -12,10 +12,10 @@ _openapi: ⚠️ Experimental endpoint: this API is in active development and may change at any time without notice. ⚠️ Returns all compute versions belonging to a compute service, ordered by creation time (newest first). Supports cursor-based pagination. - path: "/v1/compute-services/{computeServiceId}/versions" -url: "/management-api/endpoints/[experimental]/get-compute-services-by-compute-service-id-versions" -metaTitle: "GET /v1/compute-services/{computeServiceId}/versions | List compute versions" -metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Returns all compute versions belonging to a compute service, ordered by creation time (newest first). Supports cursor-based pagination." + path: '/v1/compute-services/{computeServiceId}/versions' +url: '/management-api/endpoints/[experimental]/get-compute-services-by-compute-service-id-versions' +metaTitle: 'GET /v1/compute-services/{computeServiceId}/versions | List compute versions' +metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Returns all compute versions belonging to a compute service, ordered by creation time (newest first). Supports cursor-based pagination.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,7 +24,4 @@ metaDescription: "Management API: Experimental endpoint: this API is in active d Returns all compute versions belonging to a compute service, ordered by creation time (newest first). Supports cursor-based pagination. - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services-by-compute-service-id.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services-by-compute-service-id.mdx index 026909b6d6..a1ceee61c2 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services-by-compute-service-id.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services-by-compute-service-id.mdx @@ -1,6 +1,6 @@ --- title: Get compute service -description: "Experimental endpoint: this API is in active development and may change at any time without notice. Returns a compute service by ID, including its region and latest version reference." +description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Returns a compute service by ID, including its region and latest version reference.' full: true _openapi: method: GET @@ -12,10 +12,10 @@ _openapi: ⚠️ Experimental endpoint: this API is in active development and may change at any time without notice. ⚠️ Returns a compute service by ID, including its region and latest version reference. - path: "/v1/compute-services/{computeServiceId}" -url: "/management-api/endpoints/[experimental]/get-compute-services-by-compute-service-id" -metaTitle: "GET /v1/compute-services/{computeServiceId} | Get compute service" -metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Returns a compute service by ID, including its region and latest version reference." + path: '/v1/compute-services/{computeServiceId}' +url: '/management-api/endpoints/[experimental]/get-compute-services-by-compute-service-id' +metaTitle: 'GET /v1/compute-services/{computeServiceId} | Get compute service' +metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Returns a compute service by ID, including its region and latest version reference.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,7 +24,4 @@ metaDescription: "Management API: Experimental endpoint: this API is in active d Returns a compute service by ID, including its region and latest version reference. - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services-versions-by-version-id-logs.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services-versions-by-version-id-logs.mdx index 9164d44287..a33a63e333 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services-versions-by-version-id-logs.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services-versions-by-version-id-logs.mdx @@ -12,9 +12,9 @@ _openapi: ⚠️ Experimental endpoint: this API is in active development and may change at any time without notice. ⚠️ Upgrades to a WebSocket connection that streams log output for the specified compute version. Each message is a JSON object with `type: "log"` (log text + byte metadata) or `type: "terminal"` (end-of-segment signal with reconnect cursor). The stream ends after 10 minutes; reconnect with the `cursor` query parameter to continue. - path: "/v1/compute-services/versions/{versionId}/logs" -url: "/management-api/endpoints/[experimental]/get-compute-services-versions-by-version-id-logs" -metaTitle: "GET /v1/compute-services/versions/{versionId}/logs | Stream compute version logs via WebSocket" + path: '/v1/compute-services/versions/{versionId}/logs' +url: '/management-api/endpoints/[experimental]/get-compute-services-versions-by-version-id-logs' +metaTitle: 'GET /v1/compute-services/versions/{versionId}/logs | Stream compute version logs via WebSocket' metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Upgrades to a WebSocket connection that streams log output for the specified compute version. Each message is a JSON object with `type: "log"` (log text + byte metadata) or `type: "terminal"` (end-of-segment signal with reconnect cursor). The stream ends after 10 minutes; reconnect with the `cursor` query parameter to continue.' --- @@ -24,7 +24,4 @@ metaDescription: 'Management API: Experimental endpoint: this API is in active d Upgrades to a WebSocket connection that streams log output for the specified compute version. Each message is a JSON object with `type: "log"` (log text + byte metadata) or `type: "terminal"` (end-of-segment signal with reconnect cursor). The stream ends after 10 minutes; reconnect with the `cursor` query parameter to continue. - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services-versions-by-version-id.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services-versions-by-version-id.mdx index a6750d4313..4b204bd0b7 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services-versions-by-version-id.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services-versions-by-version-id.mdx @@ -1,6 +1,6 @@ --- title: Get compute version -description: "Experimental endpoint: this API is in active development and may change at any time without notice. Returns a compute version by ID, including its current status derived from the underlying VM state." +description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Returns a compute version by ID, including its current status derived from the underlying VM state.' full: true _openapi: method: GET @@ -12,10 +12,10 @@ _openapi: ⚠️ Experimental endpoint: this API is in active development and may change at any time without notice. ⚠️ Returns a compute version by ID, including its current status derived from the underlying VM state. - path: "/v1/compute-services/versions/{versionId}" -url: "/management-api/endpoints/[experimental]/get-compute-services-versions-by-version-id" -metaTitle: "GET /v1/compute-services/versions/{versionId} | Get compute version" -metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Returns a compute version by ID, including its current status derived from the underlying VM state." + path: '/v1/compute-services/versions/{versionId}' +url: '/management-api/endpoints/[experimental]/get-compute-services-versions-by-version-id' +metaTitle: 'GET /v1/compute-services/versions/{versionId} | Get compute version' +metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Returns a compute version by ID, including its current status derived from the underlying VM state.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,7 +24,4 @@ metaDescription: "Management API: Experimental endpoint: this API is in active d Returns a compute version by ID, including its current status derived from the underlying VM state. - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services.mdx index e43dae0ed0..57d2f3cc6a 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/get-compute-services.mdx @@ -1,6 +1,6 @@ --- title: List compute services -description: "Experimental endpoint: this API is in active development and may change at any time without notice. Returns all compute services the token has access to, ordered by creation time (oldest first). Optionally filter by project ID. Supports cursor-based pagination." +description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Returns all compute services the token has access to, ordered by creation time (oldest first). Optionally filter by project ID. Supports cursor-based pagination.' full: true _openapi: method: GET @@ -13,9 +13,9 @@ _openapi: Returns all compute services the token has access to, ordered by creation time (oldest first). Optionally filter by project ID. Supports cursor-based pagination. path: /v1/compute-services -url: "/management-api/endpoints/[experimental]/get-compute-services" +url: '/management-api/endpoints/[experimental]/get-compute-services' metaTitle: GET /v1/compute-services | List compute services -metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Returns all compute services the token has access to, ordered by creation time (oldest first). Optionally filter by project ID. Supports cursor-based pagination." +metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Returns all compute services the token has access to, ordered by creation time (oldest first). Optionally filter by project ID. Supports cursor-based pagination.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,7 +24,4 @@ metaDescription: "Management API: Experimental endpoint: this API is in active d Returns all compute services the token has access to, ordered by creation time (oldest first). Optionally filter by project ID. Supports cursor-based pagination. - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/get-projects-by-project-id-compute-services.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/get-projects-by-project-id-compute-services.mdx index a6d77f6683..db07a93ba1 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/get-projects-by-project-id-compute-services.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/get-projects-by-project-id-compute-services.mdx @@ -1,6 +1,6 @@ --- title: List compute services for a project -description: "Experimental endpoint: this API is in active development and may change at any time without notice. Returns all compute services belonging to a project, ordered by creation time (oldest first). Supports cursor-based pagination." +description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Returns all compute services belonging to a project, ordered by creation time (oldest first). Supports cursor-based pagination.' full: true _openapi: method: GET @@ -12,10 +12,10 @@ _openapi: ⚠️ Experimental endpoint: this API is in active development and may change at any time without notice. ⚠️ Returns all compute services belonging to a project, ordered by creation time (oldest first). Supports cursor-based pagination. - path: "/v1/projects/{projectId}/compute-services" -url: "/management-api/endpoints/[experimental]/get-projects-by-project-id-compute-services" -metaTitle: "GET /v1/projects/{projectId}/compute-services | List compute services for a project" -metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Returns all compute services belonging to a project, ordered by creation time (oldest first). Supports cursor-based pagination." + path: '/v1/projects/{projectId}/compute-services' +url: '/management-api/endpoints/[experimental]/get-projects-by-project-id-compute-services' +metaTitle: 'GET /v1/projects/{projectId}/compute-services | List compute services for a project' +metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Returns all compute services belonging to a project, ordered by creation time (oldest first). Supports cursor-based pagination.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,7 +24,4 @@ metaDescription: "Management API: Experimental endpoint: this API is in active d Returns all compute services belonging to a project, ordered by creation time (oldest first). Supports cursor-based pagination. - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/get-versions-by-version-id.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/get-versions-by-version-id.mdx index 438afe9509..55e8a1815b 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/get-versions-by-version-id.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/get-versions-by-version-id.mdx @@ -1,6 +1,6 @@ --- title: Get compute version -description: "Experimental endpoint: this API is in active development and may change at any time without notice. Returns a compute version by ID, including its current status derived from the underlying VM state." +description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Returns a compute version by ID, including its current status derived from the underlying VM state.' full: true _openapi: method: GET @@ -12,10 +12,10 @@ _openapi: ⚠️ Experimental endpoint: this API is in active development and may change at any time without notice. ⚠️ Returns a compute version by ID, including its current status derived from the underlying VM state. - path: "/v1/versions/{versionId}" -url: "/management-api/endpoints/[experimental]/get-versions-by-version-id" -metaTitle: "GET /v1/versions/{versionId} | Get compute version" -metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Returns a compute version by ID, including its current status derived from the underlying VM state." + path: '/v1/versions/{versionId}' +url: '/management-api/endpoints/[experimental]/get-versions-by-version-id' +metaTitle: 'GET /v1/versions/{versionId} | Get compute version' +metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Returns a compute version by ID, including its current status derived from the underlying VM state.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,7 +24,4 @@ metaDescription: "Management API: Experimental endpoint: this API is in active d Returns a compute version by ID, including its current status derived from the underlying VM state. - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/get-versions.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/get-versions.mdx index 66ebbc2e1e..7fc3c5a8c1 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/get-versions.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/get-versions.mdx @@ -1,6 +1,6 @@ --- title: List compute versions -description: "Experimental endpoint: this API is in active development and may change at any time without notice. Returns all compute versions the token has access to, ordered by creation time (newest first). Optionally filter by compute service ID. Supports cursor-based pagination." +description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Returns all compute versions the token has access to, ordered by creation time (newest first). Optionally filter by compute service ID. Supports cursor-based pagination.' full: true _openapi: method: GET @@ -13,9 +13,9 @@ _openapi: Returns all compute versions the token has access to, ordered by creation time (newest first). Optionally filter by compute service ID. Supports cursor-based pagination. path: /v1/versions -url: "/management-api/endpoints/[experimental]/get-versions" +url: '/management-api/endpoints/[experimental]/get-versions' metaTitle: GET /v1/versions | List compute versions -metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Returns all compute versions the token has access to, ordered by creation time (newest first). Optionally filter by compute service ID. Supports cursor-based pagination." +metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Returns all compute versions the token has access to, ordered by creation time (newest first). Optionally filter by compute service ID. Supports cursor-based pagination.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,4 +24,4 @@ metaDescription: "Management API: Experimental endpoint: this API is in active d Returns all compute versions the token has access to, ordered by creation time (newest first). Optionally filter by compute service ID. Supports cursor-based pagination. - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/patch-compute-services-by-compute-service-id.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/patch-compute-services-by-compute-service-id.mdx index 5365e334f2..4657c8882f 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/patch-compute-services-by-compute-service-id.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/patch-compute-services-by-compute-service-id.mdx @@ -1,6 +1,6 @@ --- title: Update compute service -description: "Experimental endpoint: this API is in active development and may change at any time without notice. Updates the display name of a compute service." +description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Updates the display name of a compute service.' full: true _openapi: method: PATCH @@ -12,10 +12,10 @@ _openapi: ⚠️ Experimental endpoint: this API is in active development and may change at any time without notice. ⚠️ Updates the display name of a compute service. - path: "/v1/compute-services/{computeServiceId}" -url: "/management-api/endpoints/[experimental]/patch-compute-services-by-compute-service-id" -metaTitle: "PATCH /v1/compute-services/{computeServiceId} | Update compute service" -metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Updates the display name of a compute service." + path: '/v1/compute-services/{computeServiceId}' +url: '/management-api/endpoints/[experimental]/patch-compute-services-by-compute-service-id' +metaTitle: 'PATCH /v1/compute-services/{computeServiceId} | Update compute service' +metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Updates the display name of a compute service.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,7 +24,4 @@ metaDescription: "Management API: Experimental endpoint: this API is in active d Updates the display name of a compute service. - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services-by-compute-service-id-promote.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services-by-compute-service-id-promote.mdx index eb98efd7a7..94f3adadd3 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services-by-compute-service-id-promote.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services-by-compute-service-id-promote.mdx @@ -1,6 +1,6 @@ --- title: Promote compute version -description: "Experimental endpoint: this API is in active development and may change at any time without notice. Promotes a compute version to be the active version behind the service's stable endpoint. The version must be running. Returns the service endpoint domain." +description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Promotes a compute version to be the active version behind the service''s stable endpoint. The version must be running. Returns the service endpoint domain.' full: true _openapi: method: POST @@ -12,10 +12,10 @@ _openapi: ⚠️ Experimental endpoint: this API is in active development and may change at any time without notice. ⚠️ Promotes a compute version to be the active version behind the service's stable endpoint. The version must be running. Returns the service endpoint domain. - path: "/v1/compute-services/{computeServiceId}/promote" -url: "/management-api/endpoints/[experimental]/post-compute-services-by-compute-service-id-promote" -metaTitle: "POST /v1/compute-services/{computeServiceId}/promote | Promote compute version" -metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Promotes a compute version to be the active version behind the service's stable endpoint. The version must be running. Returns the service endpoint domain." + path: '/v1/compute-services/{computeServiceId}/promote' +url: '/management-api/endpoints/[experimental]/post-compute-services-by-compute-service-id-promote' +metaTitle: 'POST /v1/compute-services/{computeServiceId}/promote | Promote compute version' +metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Promotes a compute version to be the active version behind the service''s stable endpoint. The version must be running. Returns the service endpoint domain.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,7 +24,4 @@ metaDescription: "Management API: Experimental endpoint: this API is in active d Promotes a compute version to be the active version behind the service's stable endpoint. The version must be running. Returns the service endpoint domain. - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services-by-compute-service-id-versions.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services-by-compute-service-id-versions.mdx index bf2875bbeb..f05936c1f9 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services-by-compute-service-id-versions.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services-by-compute-service-id-versions.mdx @@ -1,6 +1,6 @@ --- title: Create compute version -description: "Experimental endpoint: this API is in active development and may change at any time without notice. Creates a new compute version under the specified compute service. Returns a pre-signed upload URL for the artifact unless `skipCodeUpload` is set (which forks the latest version's artifact). Environment variables are merged with the previous version's variables when one exists." +description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Creates a new compute version under the specified compute service. Returns a pre-signed upload URL for the artifact unless `skipCodeUpload` is set (which forks the latest version''s artifact). Environment variables are merged with the previous version''s variables when one exists.' full: true _openapi: method: POST @@ -12,10 +12,10 @@ _openapi: ⚠️ Experimental endpoint: this API is in active development and may change at any time without notice. ⚠️ Creates a new compute version under the specified compute service. Returns a pre-signed upload URL for the artifact unless `skipCodeUpload` is set (which forks the latest version's artifact). Environment variables are merged with the previous version's variables when one exists. - path: "/v1/compute-services/{computeServiceId}/versions" -url: "/management-api/endpoints/[experimental]/post-compute-services-by-compute-service-id-versions" -metaTitle: "POST /v1/compute-services/{computeServiceId}/versions | Create compute version" -metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Creates a new compute version under the specified compute service. Returns a pre-signed upload URL for the artifact unless `skipCodeUpload` is set (which forks the latest version's artifact). Environment variables are merged with the previous version's variables when one exists." + path: '/v1/compute-services/{computeServiceId}/versions' +url: '/management-api/endpoints/[experimental]/post-compute-services-by-compute-service-id-versions' +metaTitle: 'POST /v1/compute-services/{computeServiceId}/versions | Create compute version' +metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Creates a new compute version under the specified compute service. Returns a pre-signed upload URL for the artifact unless `skipCodeUpload` is set (which forks the latest version''s artifact). Environment variables are merged with the previous version''s variables when one exists.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,7 +24,4 @@ metaDescription: "Management API: Experimental endpoint: this API is in active d Creates a new compute version under the specified compute service. Returns a pre-signed upload URL for the artifact unless `skipCodeUpload` is set (which forks the latest version's artifact). Environment variables are merged with the previous version's variables when one exists. - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services-versions-by-version-id-start.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services-versions-by-version-id-start.mdx index f3c37a12ae..b35c2e3506 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services-versions-by-version-id-start.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services-versions-by-version-id-start.mdx @@ -1,6 +1,6 @@ --- title: Start compute version -description: "Experimental endpoint: this API is in active development and may change at any time without notice. Requests VM creation and startup for the compute version. The artifact must be uploaded before calling this endpoint. Returns a preview domain that becomes reachable once the VM is running. Poll the status endpoint until `running` is reached." +description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Requests VM creation and startup for the compute version. The artifact must be uploaded before calling this endpoint. Returns a preview domain that becomes reachable once the VM is running. Poll the status endpoint until `running` is reached.' full: true _openapi: method: POST @@ -12,10 +12,10 @@ _openapi: ⚠️ Experimental endpoint: this API is in active development and may change at any time without notice. ⚠️ Requests VM creation and startup for the compute version. The artifact must be uploaded before calling this endpoint. Returns a preview domain that becomes reachable once the VM is running. Poll the status endpoint until `running` is reached. - path: "/v1/compute-services/versions/{versionId}/start" -url: "/management-api/endpoints/[experimental]/post-compute-services-versions-by-version-id-start" -metaTitle: "POST /v1/compute-services/versions/{versionId}/start | Start compute version" -metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Requests VM creation and startup for the compute version. The artifact must be uploaded before calling this endpoint. Returns a preview domain that becomes reachable once the VM is running. Poll the status endpoint until `running` is reached." + path: '/v1/compute-services/versions/{versionId}/start' +url: '/management-api/endpoints/[experimental]/post-compute-services-versions-by-version-id-start' +metaTitle: 'POST /v1/compute-services/versions/{versionId}/start | Start compute version' +metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Requests VM creation and startup for the compute version. The artifact must be uploaded before calling this endpoint. Returns a preview domain that becomes reachable once the VM is running. Poll the status endpoint until `running` is reached.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,7 +24,4 @@ metaDescription: "Management API: Experimental endpoint: this API is in active d Requests VM creation and startup for the compute version. The artifact must be uploaded before calling this endpoint. Returns a preview domain that becomes reachable once the VM is running. Poll the status endpoint until `running` is reached. - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services-versions-by-version-id-stop.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services-versions-by-version-id-stop.mdx index cea7ac9e89..b19549b5ff 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services-versions-by-version-id-stop.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services-versions-by-version-id-stop.mdx @@ -1,6 +1,6 @@ --- title: Stop compute version -description: "Experimental endpoint: this API is in active development and may change at any time without notice. Requests VM shutdown for the compute version. The version record and metadata are retained. Poll the status endpoint until `stopped` is reached." +description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Requests VM shutdown for the compute version. The version record and metadata are retained. Poll the status endpoint until `stopped` is reached.' full: true _openapi: method: POST @@ -12,10 +12,10 @@ _openapi: ⚠️ Experimental endpoint: this API is in active development and may change at any time without notice. ⚠️ Requests VM shutdown for the compute version. The version record and metadata are retained. Poll the status endpoint until `stopped` is reached. - path: "/v1/compute-services/versions/{versionId}/stop" -url: "/management-api/endpoints/[experimental]/post-compute-services-versions-by-version-id-stop" -metaTitle: "POST /v1/compute-services/versions/{versionId}/stop | Stop compute version" -metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Requests VM shutdown for the compute version. The version record and metadata are retained. Poll the status endpoint until `stopped` is reached." + path: '/v1/compute-services/versions/{versionId}/stop' +url: '/management-api/endpoints/[experimental]/post-compute-services-versions-by-version-id-stop' +metaTitle: 'POST /v1/compute-services/versions/{versionId}/stop | Stop compute version' +metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Requests VM shutdown for the compute version. The version record and metadata are retained. Poll the status endpoint until `stopped` is reached.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,7 +24,4 @@ metaDescription: "Management API: Experimental endpoint: this API is in active d Requests VM shutdown for the compute version. The version record and metadata are retained. Poll the status endpoint until `stopped` is reached. - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services.mdx index 555d9ed8f2..841863e3d6 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/post-compute-services.mdx @@ -1,6 +1,6 @@ --- title: Create compute service -description: "Experimental endpoint: this API is in active development and may change at any time without notice. Creates a new compute service under the specified project. The `projectId` is required in the request body. The service is placed in the given region (or the default region if omitted)." +description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Creates a new compute service under the specified project. The `projectId` is required in the request body. The service is placed in the given region (or the default region if omitted).' full: true _openapi: method: POST @@ -13,9 +13,9 @@ _openapi: Creates a new compute service under the specified project. The `projectId` is required in the request body. The service is placed in the given region (or the default region if omitted). path: /v1/compute-services -url: "/management-api/endpoints/[experimental]/post-compute-services" +url: '/management-api/endpoints/[experimental]/post-compute-services' metaTitle: POST /v1/compute-services | Create compute service -metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Creates a new compute service under the specified project. The `projectId` is required in the request body. The service is placed in the given region (or the default region if omitted)." +metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Creates a new compute service under the specified project. The `projectId` is required in the request body. The service is placed in the given region (or the default region if omitted).' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,7 +24,4 @@ metaDescription: "Management API: Experimental endpoint: this API is in active d Creates a new compute service under the specified project. The `projectId` is required in the request body. The service is placed in the given region (or the default region if omitted). - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/post-projects-by-project-id-compute-services.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/post-projects-by-project-id-compute-services.mdx index f1ee5e532a..117ec206f9 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/post-projects-by-project-id-compute-services.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/post-projects-by-project-id-compute-services.mdx @@ -1,6 +1,6 @@ --- title: Create compute service -description: "Experimental endpoint: this API is in active development and may change at any time without notice. Creates a new compute service under the specified project. The service is placed in the given region (or the default region if omitted)." +description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Creates a new compute service under the specified project. The service is placed in the given region (or the default region if omitted).' full: true _openapi: method: POST @@ -12,10 +12,10 @@ _openapi: ⚠️ Experimental endpoint: this API is in active development and may change at any time without notice. ⚠️ Creates a new compute service under the specified project. The service is placed in the given region (or the default region if omitted). - path: "/v1/projects/{projectId}/compute-services" -url: "/management-api/endpoints/[experimental]/post-projects-by-project-id-compute-services" -metaTitle: "POST /v1/projects/{projectId}/compute-services | Create compute service" -metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Creates a new compute service under the specified project. The service is placed in the given region (or the default region if omitted)." + path: '/v1/projects/{projectId}/compute-services' +url: '/management-api/endpoints/[experimental]/post-projects-by-project-id-compute-services' +metaTitle: 'POST /v1/projects/{projectId}/compute-services | Create compute service' +metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Creates a new compute service under the specified project. The service is placed in the given region (or the default region if omitted).' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,7 +24,4 @@ metaDescription: "Management API: Experimental endpoint: this API is in active d Creates a new compute service under the specified project. The service is placed in the given region (or the default region if omitted). - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/post-versions-by-version-id-start.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/post-versions-by-version-id-start.mdx index b3ea1c5668..30504cd12a 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/post-versions-by-version-id-start.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/post-versions-by-version-id-start.mdx @@ -1,6 +1,6 @@ --- title: Start compute version -description: "Experimental endpoint: this API is in active development and may change at any time without notice. Requests VM creation and startup for the compute version. The artifact must be uploaded before calling this endpoint. Returns a preview domain that becomes reachable once the VM is running. Poll the status endpoint until `running` is reached." +description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Requests VM creation and startup for the compute version. The artifact must be uploaded before calling this endpoint. Returns a preview domain that becomes reachable once the VM is running. Poll the status endpoint until `running` is reached.' full: true _openapi: method: POST @@ -12,10 +12,10 @@ _openapi: ⚠️ Experimental endpoint: this API is in active development and may change at any time without notice. ⚠️ Requests VM creation and startup for the compute version. The artifact must be uploaded before calling this endpoint. Returns a preview domain that becomes reachable once the VM is running. Poll the status endpoint until `running` is reached. - path: "/v1/versions/{versionId}/start" -url: "/management-api/endpoints/[experimental]/post-versions-by-version-id-start" -metaTitle: "POST /v1/versions/{versionId}/start | Start compute version" -metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Requests VM creation and startup for the compute version. The artifact must be uploaded before calling this endpoint. Returns a preview domain that becomes reachable once the VM is running. Poll the status endpoint until `running` is reached." + path: '/v1/versions/{versionId}/start' +url: '/management-api/endpoints/[experimental]/post-versions-by-version-id-start' +metaTitle: 'POST /v1/versions/{versionId}/start | Start compute version' +metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Requests VM creation and startup for the compute version. The artifact must be uploaded before calling this endpoint. Returns a preview domain that becomes reachable once the VM is running. Poll the status endpoint until `running` is reached.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,7 +24,4 @@ metaDescription: "Management API: Experimental endpoint: this API is in active d Requests VM creation and startup for the compute version. The artifact must be uploaded before calling this endpoint. Returns a preview domain that becomes reachable once the VM is running. Poll the status endpoint until `running` is reached. - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/post-versions-by-version-id-stop.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/post-versions-by-version-id-stop.mdx index debd49c488..0691200326 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/post-versions-by-version-id-stop.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/post-versions-by-version-id-stop.mdx @@ -1,6 +1,6 @@ --- title: Stop compute version -description: "Experimental endpoint: this API is in active development and may change at any time without notice. Requests VM shutdown for the compute version. The version record and metadata are retained. Poll the status endpoint until `stopped` is reached." +description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Requests VM shutdown for the compute version. The version record and metadata are retained. Poll the status endpoint until `stopped` is reached.' full: true _openapi: method: POST @@ -12,10 +12,10 @@ _openapi: ⚠️ Experimental endpoint: this API is in active development and may change at any time without notice. ⚠️ Requests VM shutdown for the compute version. The version record and metadata are retained. Poll the status endpoint until `stopped` is reached. - path: "/v1/versions/{versionId}/stop" -url: "/management-api/endpoints/[experimental]/post-versions-by-version-id-stop" -metaTitle: "POST /v1/versions/{versionId}/stop | Stop compute version" -metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Requests VM shutdown for the compute version. The version record and metadata are retained. Poll the status endpoint until `stopped` is reached." + path: '/v1/versions/{versionId}/stop' +url: '/management-api/endpoints/[experimental]/post-versions-by-version-id-stop' +metaTitle: 'POST /v1/versions/{versionId}/stop | Stop compute version' +metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Requests VM shutdown for the compute version. The version record and metadata are retained. Poll the status endpoint until `stopped` is reached.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,7 +24,4 @@ metaDescription: "Management API: Experimental endpoint: this API is in active d Requests VM shutdown for the compute version. The version record and metadata are retained. Poll the status endpoint until `stopped` is reached. - + diff --git a/apps/docs/content/docs/management-api/endpoints/[experimental]/post-versions.mdx b/apps/docs/content/docs/management-api/endpoints/[experimental]/post-versions.mdx index 6f5e9b58f4..58565e656d 100644 --- a/apps/docs/content/docs/management-api/endpoints/[experimental]/post-versions.mdx +++ b/apps/docs/content/docs/management-api/endpoints/[experimental]/post-versions.mdx @@ -1,6 +1,6 @@ --- title: Create compute version -description: "Experimental endpoint: this API is in active development and may change at any time without notice. Creates a new compute version under the specified compute service. The `computeServiceId` is required in the request body. Returns a pre-signed upload URL for the artifact unless `skipCodeUpload` is set. Environment variables are merged with the previous version's variables when one exists." +description: 'Experimental endpoint: this API is in active development and may change at any time without notice. Creates a new compute version under the specified compute service. The `computeServiceId` is required in the request body. Returns a pre-signed upload URL for the artifact unless `skipCodeUpload` is set. Environment variables are merged with the previous version''s variables when one exists.' full: true _openapi: method: POST @@ -13,9 +13,9 @@ _openapi: Creates a new compute version under the specified compute service. The `computeServiceId` is required in the request body. Returns a pre-signed upload URL for the artifact unless `skipCodeUpload` is set. Environment variables are merged with the previous version's variables when one exists. path: /v1/versions -url: "/management-api/endpoints/[experimental]/post-versions" +url: '/management-api/endpoints/[experimental]/post-versions' metaTitle: POST /v1/versions | Create compute version -metaDescription: "Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Creates a new compute version under the specified compute service. The `computeServiceId` is required in the request body. Returns a pre-signed upload URL for the artifact unless `skipCodeUpload` is set. Environment variables are merged with the previous version's variables when one exists." +metaDescription: 'Management API: Experimental endpoint: this API is in active development and may change at any time without notice. Creates a new compute version under the specified compute service. The `computeServiceId` is required in the request body. Returns a pre-signed upload URL for the artifact unless `skipCodeUpload` is set. Environment variables are merged with the previous version''s variables when one exists.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,4 +24,4 @@ metaDescription: "Management API: Experimental endpoint: this API is in active d Creates a new compute version under the specified compute service. The `computeServiceId` is required in the request body. Returns a pre-signed upload URL for the artifact unless `skipCodeUpload` is set. Environment variables are merged with the previous version's variables when one exists. - + diff --git a/apps/docs/content/docs/management-api/endpoints/connections/delete-connections-by-id.mdx b/apps/docs/content/docs/management-api/endpoints/connections/delete-connections-by-id.mdx index ee05fd8957..e005accaf6 100644 --- a/apps/docs/content/docs/management-api/endpoints/connections/delete-connections-by-id.mdx +++ b/apps/docs/content/docs/management-api/endpoints/connections/delete-connections-by-id.mdx @@ -9,17 +9,14 @@ _openapi: headings: [] contents: - content: Deletes the connection with the given ID. - path: "/v1/connections/{id}" + path: '/v1/connections/{id}' url: /management-api/endpoints/connections/delete-connections-by-id -metaTitle: "DELETE /v1/connections/{id} | Delete connection" -metaDescription: "Management API: Deletes the connection with the given ID." +metaTitle: 'DELETE /v1/connections/{id} | Delete connection' +metaDescription: 'Management API: Deletes the connection with the given ID.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Deletes the connection with the given ID. - + diff --git a/apps/docs/content/docs/management-api/endpoints/connections/get-connections-by-id.mdx b/apps/docs/content/docs/management-api/endpoints/connections/get-connections-by-id.mdx index fa08da2fc0..1cc3cd0e93 100644 --- a/apps/docs/content/docs/management-api/endpoints/connections/get-connections-by-id.mdx +++ b/apps/docs/content/docs/management-api/endpoints/connections/get-connections-by-id.mdx @@ -9,17 +9,14 @@ _openapi: headings: [] contents: - content: Returns the connection with the given ID. - path: "/v1/connections/{id}" + path: '/v1/connections/{id}' url: /management-api/endpoints/connections/get-connections-by-id -metaTitle: "GET /v1/connections/{id} | Get connection" -metaDescription: "Management API: Returns the connection with the given ID." +metaTitle: 'GET /v1/connections/{id} | Get connection' +metaDescription: 'Management API: Returns the connection with the given ID.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Returns the connection with the given ID. - + diff --git a/apps/docs/content/docs/management-api/endpoints/connections/get-connections.mdx b/apps/docs/content/docs/management-api/endpoints/connections/get-connections.mdx index d051330cc7..e701da7394 100644 --- a/apps/docs/content/docs/management-api/endpoints/connections/get-connections.mdx +++ b/apps/docs/content/docs/management-api/endpoints/connections/get-connections.mdx @@ -1,6 +1,6 @@ --- title: List connections -description: "Returns all connections the actor has access to, with optional database filter." +description: 'Returns all connections the actor has access to, with optional database filter.' full: true _openapi: method: GET @@ -8,15 +8,15 @@ _openapi: structuredData: headings: [] contents: - - content: "Returns all connections the actor has access to, with optional database filter." + - content: 'Returns all connections the actor has access to, with optional database filter.' path: /v1/connections url: /management-api/endpoints/connections/get-connections metaTitle: GET /v1/connections | List connections -metaDescription: "Management API: Returns all connections the actor has access to, with optional database filter." +metaDescription: 'Management API: Returns all connections the actor has access to, with optional database filter.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Returns all connections the actor has access to, with optional database filter. - + diff --git a/apps/docs/content/docs/management-api/endpoints/connections/post-connections-by-id-rotate.mdx b/apps/docs/content/docs/management-api/endpoints/connections/post-connections-by-id-rotate.mdx index aba8b4b942..ae7187406f 100644 --- a/apps/docs/content/docs/management-api/endpoints/connections/post-connections-by-id-rotate.mdx +++ b/apps/docs/content/docs/management-api/endpoints/connections/post-connections-by-id-rotate.mdx @@ -9,17 +9,14 @@ _openapi: headings: [] contents: - content: Generates new credentials for the connection with the given ID. Revocation of the previous credentials is best-effort. - path: "/v1/connections/{id}/rotate" + path: '/v1/connections/{id}/rotate' url: /management-api/endpoints/connections/post-connections-by-id-rotate -metaTitle: "POST /v1/connections/{id}/rotate | Rotate connection credentials" -metaDescription: "Management API: Generates new credentials for the connection with the given ID. Revocation of the previous credentials is best-effort." +metaTitle: 'POST /v1/connections/{id}/rotate | Rotate connection credentials' +metaDescription: 'Management API: Generates new credentials for the connection with the given ID. Revocation of the previous credentials is best-effort.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Generates new credentials for the connection with the given ID. Revocation of the previous credentials is best-effort. - + diff --git a/apps/docs/content/docs/management-api/endpoints/connections/post-connections.mdx b/apps/docs/content/docs/management-api/endpoints/connections/post-connections.mdx index 3fff7112e2..bf3c0b7e82 100644 --- a/apps/docs/content/docs/management-api/endpoints/connections/post-connections.mdx +++ b/apps/docs/content/docs/management-api/endpoints/connections/post-connections.mdx @@ -12,11 +12,11 @@ _openapi: path: /v1/connections url: /management-api/endpoints/connections/post-connections metaTitle: POST /v1/connections | Create connection -metaDescription: "Management API: Creates a new connection for the specified database." +metaDescription: 'Management API: Creates a new connection for the specified database.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Creates a new connection for the specified database. - + diff --git a/apps/docs/content/docs/management-api/endpoints/database-backups/get-databases-by-database-id-backups.mdx b/apps/docs/content/docs/management-api/endpoints/database-backups/get-databases-by-database-id-backups.mdx index ab1e553261..fcb96f1a67 100644 --- a/apps/docs/content/docs/management-api/endpoints/database-backups/get-databases-by-database-id-backups.mdx +++ b/apps/docs/content/docs/management-api/endpoints/database-backups/get-databases-by-database-id-backups.mdx @@ -9,17 +9,14 @@ _openapi: headings: [] contents: - content: Returns backups for the specified database. - path: "/v1/databases/{databaseId}/backups" + path: '/v1/databases/{databaseId}/backups' url: /management-api/endpoints/database-backups/get-databases-by-database-id-backups -metaTitle: "GET /v1/databases/{databaseId}/backups | Get list of backups" -metaDescription: "Management API: Returns backups for the specified database." +metaTitle: 'GET /v1/databases/{databaseId}/backups | Get list of backups' +metaDescription: 'Management API: Returns backups for the specified database.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Returns backups for the specified database. - + diff --git a/apps/docs/content/docs/management-api/endpoints/database-usage/get-databases-by-database-id-usage.mdx b/apps/docs/content/docs/management-api/endpoints/database-usage/get-databases-by-database-id-usage.mdx index 312d83c635..e03e0cfe1a 100644 --- a/apps/docs/content/docs/management-api/endpoints/database-usage/get-databases-by-database-id-usage.mdx +++ b/apps/docs/content/docs/management-api/endpoints/database-usage/get-databases-by-database-id-usage.mdx @@ -9,17 +9,14 @@ _openapi: headings: [] contents: - content: Returns usage metrics for the specified database. - path: "/v1/databases/{databaseId}/usage" + path: '/v1/databases/{databaseId}/usage' url: /management-api/endpoints/database-usage/get-databases-by-database-id-usage -metaTitle: "GET /v1/databases/{databaseId}/usage | Get database usage metrics" -metaDescription: "Management API: Returns usage metrics for the specified database." +metaTitle: 'GET /v1/databases/{databaseId}/usage | Get database usage metrics' +metaDescription: 'Management API: Returns usage metrics for the specified database.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Returns usage metrics for the specified database. - + diff --git a/apps/docs/content/docs/management-api/endpoints/databases-connections/get-databases-by-database-id-connections.mdx b/apps/docs/content/docs/management-api/endpoints/databases-connections/get-databases-by-database-id-connections.mdx index 7980983561..632f721123 100644 --- a/apps/docs/content/docs/management-api/endpoints/databases-connections/get-databases-by-database-id-connections.mdx +++ b/apps/docs/content/docs/management-api/endpoints/databases-connections/get-databases-by-database-id-connections.mdx @@ -9,17 +9,14 @@ _openapi: headings: [] contents: - content: Returns all connections for the given database. - path: "/v1/databases/{databaseId}/connections" + path: '/v1/databases/{databaseId}/connections' url: /management-api/endpoints/databases-connections/get-databases-by-database-id-connections -metaTitle: "GET /v1/databases/{databaseId}/connections | Get list of database connections" -metaDescription: "Management API: Returns all connections for the given database." +metaTitle: 'GET /v1/databases/{databaseId}/connections | Get list of database connections' +metaDescription: 'Management API: Returns all connections for the given database.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Returns all connections for the given database. - + diff --git a/apps/docs/content/docs/management-api/endpoints/databases-connections/post-databases-by-database-id-connections.mdx b/apps/docs/content/docs/management-api/endpoints/databases-connections/post-databases-by-database-id-connections.mdx index 9e1849c931..50d0f41307 100644 --- a/apps/docs/content/docs/management-api/endpoints/databases-connections/post-databases-by-database-id-connections.mdx +++ b/apps/docs/content/docs/management-api/endpoints/databases-connections/post-databases-by-database-id-connections.mdx @@ -9,17 +9,14 @@ _openapi: headings: [] contents: - content: Creates a new connection string for the given database. - path: "/v1/databases/{databaseId}/connections" + path: '/v1/databases/{databaseId}/connections' url: /management-api/endpoints/databases-connections/post-databases-by-database-id-connections -metaTitle: "POST /v1/databases/{databaseId}/connections | Create database connection string" -metaDescription: "Management API: Creates a new connection string for the given database." +metaTitle: 'POST /v1/databases/{databaseId}/connections | Create database connection string' +metaDescription: 'Management API: Creates a new connection string for the given database.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Creates a new connection string for the given database. - + diff --git a/apps/docs/content/docs/management-api/endpoints/databases/delete-databases-by-database-id.mdx b/apps/docs/content/docs/management-api/endpoints/databases/delete-databases-by-database-id.mdx index 094ec72957..2f9342b51c 100644 --- a/apps/docs/content/docs/management-api/endpoints/databases/delete-databases-by-database-id.mdx +++ b/apps/docs/content/docs/management-api/endpoints/databases/delete-databases-by-database-id.mdx @@ -9,17 +9,14 @@ _openapi: headings: [] contents: - content: Deletes the database with the given ID. - path: "/v1/databases/{databaseId}" + path: '/v1/databases/{databaseId}' url: /management-api/endpoints/databases/delete-databases-by-database-id -metaTitle: "DELETE /v1/databases/{databaseId} | Delete database" -metaDescription: "Management API: Deletes the database with the given ID." +metaTitle: 'DELETE /v1/databases/{databaseId} | Delete database' +metaDescription: 'Management API: Deletes the database with the given ID.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Deletes the database with the given ID. - + diff --git a/apps/docs/content/docs/management-api/endpoints/databases/get-databases-by-database-id.mdx b/apps/docs/content/docs/management-api/endpoints/databases/get-databases-by-database-id.mdx index 78b900940b..47f9a36c54 100644 --- a/apps/docs/content/docs/management-api/endpoints/databases/get-databases-by-database-id.mdx +++ b/apps/docs/content/docs/management-api/endpoints/databases/get-databases-by-database-id.mdx @@ -9,17 +9,14 @@ _openapi: headings: [] contents: - content: Returns the database with the given ID. - path: "/v1/databases/{databaseId}" + path: '/v1/databases/{databaseId}' url: /management-api/endpoints/databases/get-databases-by-database-id -metaTitle: "GET /v1/databases/{databaseId} | Get database" -metaDescription: "Management API: Returns the database with the given ID." +metaTitle: 'GET /v1/databases/{databaseId} | Get database' +metaDescription: 'Management API: Returns the database with the given ID.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Returns the database with the given ID. - + diff --git a/apps/docs/content/docs/management-api/endpoints/databases/get-databases.mdx b/apps/docs/content/docs/management-api/endpoints/databases/get-databases.mdx index 4cf3c009f6..72c90ced63 100644 --- a/apps/docs/content/docs/management-api/endpoints/databases/get-databases.mdx +++ b/apps/docs/content/docs/management-api/endpoints/databases/get-databases.mdx @@ -12,11 +12,11 @@ _openapi: path: /v1/databases url: /management-api/endpoints/databases/get-databases metaTitle: GET /v1/databases | List databases -metaDescription: "Management API: Returns all databases the token has access to. Optionally filter by project ID." +metaDescription: 'Management API: Returns all databases the token has access to. Optionally filter by project ID.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Returns all databases the token has access to. Optionally filter by project ID. - + diff --git a/apps/docs/content/docs/management-api/endpoints/databases/get-projects-by-project-id-databases.mdx b/apps/docs/content/docs/management-api/endpoints/databases/get-projects-by-project-id-databases.mdx index 854350aebb..8e05672345 100644 --- a/apps/docs/content/docs/management-api/endpoints/databases/get-projects-by-project-id-databases.mdx +++ b/apps/docs/content/docs/management-api/endpoints/databases/get-projects-by-project-id-databases.mdx @@ -9,17 +9,14 @@ _openapi: headings: [] contents: - content: Returns databases for the given project. - path: "/v1/projects/{projectId}/databases" + path: '/v1/projects/{projectId}/databases' url: /management-api/endpoints/databases/get-projects-by-project-id-databases -metaTitle: "GET /v1/projects/{projectId}/databases | Get list of databases" -metaDescription: "Management API: Returns databases for the given project." +metaTitle: 'GET /v1/projects/{projectId}/databases | Get list of databases' +metaDescription: 'Management API: Returns databases for the given project.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Returns databases for the given project. - + diff --git a/apps/docs/content/docs/management-api/endpoints/databases/patch-databases-by-database-id.mdx b/apps/docs/content/docs/management-api/endpoints/databases/patch-databases-by-database-id.mdx index d7bede3d4e..4a0712b314 100644 --- a/apps/docs/content/docs/management-api/endpoints/databases/patch-databases-by-database-id.mdx +++ b/apps/docs/content/docs/management-api/endpoints/databases/patch-databases-by-database-id.mdx @@ -9,17 +9,14 @@ _openapi: headings: [] contents: - content: Updates the database with the given ID. - path: "/v1/databases/{databaseId}" + path: '/v1/databases/{databaseId}' url: /management-api/endpoints/databases/patch-databases-by-database-id -metaTitle: "PATCH /v1/databases/{databaseId} | Update database" -metaDescription: "Management API: Updates the database with the given ID." +metaTitle: 'PATCH /v1/databases/{databaseId} | Update database' +metaDescription: 'Management API: Updates the database with the given ID.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Updates the database with the given ID. - + diff --git a/apps/docs/content/docs/management-api/endpoints/databases/post-databases-by-target-database-id-restore.mdx b/apps/docs/content/docs/management-api/endpoints/databases/post-databases-by-target-database-id-restore.mdx index aeb2605265..86ec1b0ab5 100644 --- a/apps/docs/content/docs/management-api/endpoints/databases/post-databases-by-target-database-id-restore.mdx +++ b/apps/docs/content/docs/management-api/endpoints/databases/post-databases-by-target-database-id-restore.mdx @@ -1,6 +1,6 @@ --- title: Restore database (destructive) -description: "**Destructive operation** — this immediately and irreversibly overwrites all data in the target database with the contents of the specified backup. Any data written since the backup was taken will be lost. Ensure you have a recent backup of the target database before proceeding. Replaces the data in an existing database from a backup. Connections and credentials are preserved — only the data layer is replaced." +description: '**Destructive operation** — this immediately and irreversibly overwrites all data in the target database with the contents of the specified backup. Any data written since the backup was taken will be lost. Ensure you have a recent backup of the target database before proceeding. Replaces the data in an existing database from a backup. Connections and credentials are preserved — only the data layer is replaced.' full: true _openapi: method: POST @@ -12,10 +12,10 @@ _openapi: ⚠️ **Destructive operation** — this immediately and irreversibly overwrites all data in the target database with the contents of the specified backup. Any data written since the backup was taken will be lost. Ensure you have a recent backup of the target database before proceeding. Replaces the data in an existing database from a backup. Connections and credentials are preserved — only the data layer is replaced. - path: "/v1/databases/{targetDatabaseId}/restore" + path: '/v1/databases/{targetDatabaseId}/restore' url: /management-api/endpoints/databases/post-databases-by-target-database-id-restore -metaTitle: "POST /v1/databases/{targetDatabaseId}/restore | Restore database (destructive)" -metaDescription: "Management API: **Destructive operation** — this immediately and irreversibly overwrites all data in the target database with the contents of the specified backup. Any data written since the backup was taken will be lost. Ensure you have a recent backup of the target database before proceeding. Replaces the data in an existing database from a backup. Connections and credentials are preserved — only the data layer is replaced." +metaTitle: 'POST /v1/databases/{targetDatabaseId}/restore | Restore database (destructive)' +metaDescription: 'Management API: **Destructive operation** — this immediately and irreversibly overwrites all data in the target database with the contents of the specified backup. Any data written since the backup was taken will be lost. Ensure you have a recent backup of the target database before proceeding. Replaces the data in an existing database from a backup. Connections and credentials are preserved — only the data layer is replaced.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} @@ -24,7 +24,4 @@ metaDescription: "Management API: **Destructive operation** — this immediately Replaces the data in an existing database from a backup. Connections and credentials are preserved — only the data layer is replaced. - + diff --git a/apps/docs/content/docs/management-api/endpoints/databases/post-databases.mdx b/apps/docs/content/docs/management-api/endpoints/databases/post-databases.mdx index d82697e1c6..584afa8ebc 100644 --- a/apps/docs/content/docs/management-api/endpoints/databases/post-databases.mdx +++ b/apps/docs/content/docs/management-api/endpoints/databases/post-databases.mdx @@ -12,11 +12,11 @@ _openapi: path: /v1/databases url: /management-api/endpoints/databases/post-databases metaTitle: POST /v1/databases | Create database -metaDescription: "Management API: Creates a new database in the specified project." +metaDescription: 'Management API: Creates a new database in the specified project.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Creates a new database in the specified project. - + diff --git a/apps/docs/content/docs/management-api/endpoints/databases/post-projects-by-project-id-databases.mdx b/apps/docs/content/docs/management-api/endpoints/databases/post-projects-by-project-id-databases.mdx index 0926253fa6..5145461545 100644 --- a/apps/docs/content/docs/management-api/endpoints/databases/post-projects-by-project-id-databases.mdx +++ b/apps/docs/content/docs/management-api/endpoints/databases/post-projects-by-project-id-databases.mdx @@ -9,17 +9,14 @@ _openapi: headings: [] contents: - content: Creates a new database for the given project. - path: "/v1/projects/{projectId}/databases" + path: '/v1/projects/{projectId}/databases' url: /management-api/endpoints/databases/post-projects-by-project-id-databases -metaTitle: "POST /v1/projects/{projectId}/databases | Create database" -metaDescription: "Management API: Creates a new database for the given project." +metaTitle: 'POST /v1/projects/{projectId}/databases | Create database' +metaDescription: 'Management API: Creates a new database for the given project.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Creates a new database for the given project. - + diff --git a/apps/docs/content/docs/management-api/endpoints/integrations/delete-integrations-by-id.mdx b/apps/docs/content/docs/management-api/endpoints/integrations/delete-integrations-by-id.mdx index 62dae6e502..1834d70d96 100644 --- a/apps/docs/content/docs/management-api/endpoints/integrations/delete-integrations-by-id.mdx +++ b/apps/docs/content/docs/management-api/endpoints/integrations/delete-integrations-by-id.mdx @@ -9,17 +9,14 @@ _openapi: headings: [] contents: - content: Revokes the integration tokens by integration ID. - path: "/v1/integrations/{id}" + path: '/v1/integrations/{id}' url: /management-api/endpoints/integrations/delete-integrations-by-id -metaTitle: "DELETE /v1/integrations/{id} | Delete integration" -metaDescription: "Management API: Revokes the integration tokens by integration ID." +metaTitle: 'DELETE /v1/integrations/{id} | Delete integration' +metaDescription: 'Management API: Revokes the integration tokens by integration ID.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Revokes the integration tokens by integration ID. - + diff --git a/apps/docs/content/docs/management-api/endpoints/integrations/delete-workspaces-by-workspace-id-integrations-by-client-id.mdx b/apps/docs/content/docs/management-api/endpoints/integrations/delete-workspaces-by-workspace-id-integrations-by-client-id.mdx index 94aeb02b00..5e01a165b3 100644 --- a/apps/docs/content/docs/management-api/endpoints/integrations/delete-workspaces-by-workspace-id-integrations-by-client-id.mdx +++ b/apps/docs/content/docs/management-api/endpoints/integrations/delete-workspaces-by-workspace-id-integrations-by-client-id.mdx @@ -9,17 +9,14 @@ _openapi: headings: [] contents: - content: Revokes the integration tokens with the given client ID. - path: "/v1/workspaces/{workspaceId}/integrations/{clientId}" + path: '/v1/workspaces/{workspaceId}/integrations/{clientId}' url: /management-api/endpoints/integrations/delete-workspaces-by-workspace-id-integrations-by-client-id -metaTitle: "DELETE /v1/workspaces/{workspaceId}/integrations/{clientId} | Revoke integration tokens" -metaDescription: "Management API: Revokes the integration tokens with the given client ID." +metaTitle: 'DELETE /v1/workspaces/{workspaceId}/integrations/{clientId} | Revoke integration tokens' +metaDescription: 'Management API: Revokes the integration tokens with the given client ID.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Revokes the integration tokens with the given client ID. - + diff --git a/apps/docs/content/docs/management-api/endpoints/integrations/get-integrations-by-id.mdx b/apps/docs/content/docs/management-api/endpoints/integrations/get-integrations-by-id.mdx index 854e6fe8f8..eb5ae6cc2e 100644 --- a/apps/docs/content/docs/management-api/endpoints/integrations/get-integrations-by-id.mdx +++ b/apps/docs/content/docs/management-api/endpoints/integrations/get-integrations-by-id.mdx @@ -9,17 +9,14 @@ _openapi: headings: [] contents: - content: Returns a single integration by its ID. - path: "/v1/integrations/{id}" + path: '/v1/integrations/{id}' url: /management-api/endpoints/integrations/get-integrations-by-id -metaTitle: "GET /v1/integrations/{id} | Get integration by ID" -metaDescription: "Management API: Returns a single integration by its ID." +metaTitle: 'GET /v1/integrations/{id} | Get integration by ID' +metaDescription: 'Management API: Returns a single integration by its ID.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Returns a single integration by its ID. - + diff --git a/apps/docs/content/docs/management-api/endpoints/integrations/get-integrations.mdx b/apps/docs/content/docs/management-api/endpoints/integrations/get-integrations.mdx index 314d5d340a..d5a9a79bb8 100644 --- a/apps/docs/content/docs/management-api/endpoints/integrations/get-integrations.mdx +++ b/apps/docs/content/docs/management-api/endpoints/integrations/get-integrations.mdx @@ -12,11 +12,11 @@ _openapi: path: /v1/integrations url: /management-api/endpoints/integrations/get-integrations metaTitle: GET /v1/integrations | Get list of integrations -metaDescription: "Management API: Returns integrations filtered by workspace ID." +metaDescription: 'Management API: Returns integrations filtered by workspace ID.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Returns integrations filtered by workspace ID. - + diff --git a/apps/docs/content/docs/management-api/endpoints/integrations/get-workspaces-by-workspace-id-integrations.mdx b/apps/docs/content/docs/management-api/endpoints/integrations/get-workspaces-by-workspace-id-integrations.mdx index 2ae252cc41..1c0ff8a207 100644 --- a/apps/docs/content/docs/management-api/endpoints/integrations/get-workspaces-by-workspace-id-integrations.mdx +++ b/apps/docs/content/docs/management-api/endpoints/integrations/get-workspaces-by-workspace-id-integrations.mdx @@ -9,17 +9,14 @@ _openapi: headings: [] contents: - content: Returns integrations for the given workspace. - path: "/v1/workspaces/{workspaceId}/integrations" + path: '/v1/workspaces/{workspaceId}/integrations' url: /management-api/endpoints/integrations/get-workspaces-by-workspace-id-integrations -metaTitle: "GET /v1/workspaces/{workspaceId}/integrations | Get list of integrations" -metaDescription: "Management API: Returns integrations for the given workspace." +metaTitle: 'GET /v1/workspaces/{workspaceId}/integrations | Get list of integrations' +metaDescription: 'Management API: Returns integrations for the given workspace.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Returns integrations for the given workspace. - + diff --git a/apps/docs/content/docs/management-api/endpoints/misc/get-regions-accelerate.mdx b/apps/docs/content/docs/management-api/endpoints/misc/get-regions-accelerate.mdx index 3f1eec4a6c..53d9be6d61 100644 --- a/apps/docs/content/docs/management-api/endpoints/misc/get-regions-accelerate.mdx +++ b/apps/docs/content/docs/management-api/endpoints/misc/get-regions-accelerate.mdx @@ -12,14 +12,11 @@ _openapi: path: /v1/regions/accelerate url: /management-api/endpoints/misc/get-regions-accelerate metaTitle: GET /v1/regions/accelerate | Get Prisma Accelerate regions -metaDescription: "Management API: Returns all available regions for Prisma Accelerate." +metaDescription: 'Management API: Returns all available regions for Prisma Accelerate.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Returns all available regions for Prisma Accelerate. - + diff --git a/apps/docs/content/docs/management-api/endpoints/misc/get-regions-postgres.mdx b/apps/docs/content/docs/management-api/endpoints/misc/get-regions-postgres.mdx index 4ba66e0577..e801a01325 100644 --- a/apps/docs/content/docs/management-api/endpoints/misc/get-regions-postgres.mdx +++ b/apps/docs/content/docs/management-api/endpoints/misc/get-regions-postgres.mdx @@ -12,14 +12,11 @@ _openapi: path: /v1/regions/postgres url: /management-api/endpoints/misc/get-regions-postgres metaTitle: GET /v1/regions/postgres | Get Prisma Postgres regions -metaDescription: "Management API: Returns all available regions for Prisma Postgres." +metaDescription: 'Management API: Returns all available regions for Prisma Postgres.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Returns all available regions for Prisma Postgres. - + diff --git a/apps/docs/content/docs/management-api/endpoints/projects/delete-projects-by-id.mdx b/apps/docs/content/docs/management-api/endpoints/projects/delete-projects-by-id.mdx index b25be880ac..55c911bba4 100644 --- a/apps/docs/content/docs/management-api/endpoints/projects/delete-projects-by-id.mdx +++ b/apps/docs/content/docs/management-api/endpoints/projects/delete-projects-by-id.mdx @@ -9,17 +9,14 @@ _openapi: headings: [] contents: - content: Deletes the project with the given ID. - path: "/v1/projects/{id}" + path: '/v1/projects/{id}' url: /management-api/endpoints/projects/delete-projects-by-id -metaTitle: "DELETE /v1/projects/{id} | Delete project" -metaDescription: "Management API: Deletes the project with the given ID." +metaTitle: 'DELETE /v1/projects/{id} | Delete project' +metaDescription: 'Management API: Deletes the project with the given ID.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Deletes the project with the given ID. - + diff --git a/apps/docs/content/docs/management-api/endpoints/projects/get-projects-by-id.mdx b/apps/docs/content/docs/management-api/endpoints/projects/get-projects-by-id.mdx index efd3af4800..e57b7d3455 100644 --- a/apps/docs/content/docs/management-api/endpoints/projects/get-projects-by-id.mdx +++ b/apps/docs/content/docs/management-api/endpoints/projects/get-projects-by-id.mdx @@ -9,14 +9,14 @@ _openapi: headings: [] contents: - content: Returns the project with the given ID. - path: "/v1/projects/{id}" + path: '/v1/projects/{id}' url: /management-api/endpoints/projects/get-projects-by-id -metaTitle: "GET /v1/projects/{id} | Get project" -metaDescription: "Management API: Returns the project with the given ID." +metaTitle: 'GET /v1/projects/{id} | Get project' +metaDescription: 'Management API: Returns the project with the given ID.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Returns the project with the given ID. - + diff --git a/apps/docs/content/docs/management-api/endpoints/projects/get-projects.mdx b/apps/docs/content/docs/management-api/endpoints/projects/get-projects.mdx index 9efbb8acc6..43af5e57b7 100644 --- a/apps/docs/content/docs/management-api/endpoints/projects/get-projects.mdx +++ b/apps/docs/content/docs/management-api/endpoints/projects/get-projects.mdx @@ -12,11 +12,11 @@ _openapi: path: /v1/projects url: /management-api/endpoints/projects/get-projects metaTitle: GET /v1/projects | Get list of projects -metaDescription: "Management API: Returns the list of projects the token has access to." +metaDescription: 'Management API: Returns the list of projects the token has access to.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Returns the list of projects the token has access to. - + diff --git a/apps/docs/content/docs/management-api/endpoints/projects/patch-projects-by-id.mdx b/apps/docs/content/docs/management-api/endpoints/projects/patch-projects-by-id.mdx index 5ee6bb3c7d..3bf173a402 100644 --- a/apps/docs/content/docs/management-api/endpoints/projects/patch-projects-by-id.mdx +++ b/apps/docs/content/docs/management-api/endpoints/projects/patch-projects-by-id.mdx @@ -9,17 +9,14 @@ _openapi: headings: [] contents: - content: Updates the project with the given ID. - path: "/v1/projects/{id}" + path: '/v1/projects/{id}' url: /management-api/endpoints/projects/patch-projects-by-id -metaTitle: "PATCH /v1/projects/{id} | Update project" -metaDescription: "Management API: Updates the project with the given ID." +metaTitle: 'PATCH /v1/projects/{id} | Update project' +metaDescription: 'Management API: Updates the project with the given ID.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Updates the project with the given ID. - + diff --git a/apps/docs/content/docs/management-api/endpoints/projects/post-projects-by-id-transfer.mdx b/apps/docs/content/docs/management-api/endpoints/projects/post-projects-by-id-transfer.mdx index 142d0d9deb..22da5c5155 100644 --- a/apps/docs/content/docs/management-api/endpoints/projects/post-projects-by-id-transfer.mdx +++ b/apps/docs/content/docs/management-api/endpoints/projects/post-projects-by-id-transfer.mdx @@ -9,17 +9,14 @@ _openapi: headings: [] contents: - content: Transfer the project with the given ID to the new owner's workspace - path: "/v1/projects/{id}/transfer" + path: '/v1/projects/{id}/transfer' url: /management-api/endpoints/projects/post-projects-by-id-transfer -metaTitle: "POST /v1/projects/{id}/transfer | Transfer project" -metaDescription: "Management API: Transfer the project with the given ID to the new owner's workspace" +metaTitle: 'POST /v1/projects/{id}/transfer | Transfer project' +metaDescription: 'Management API: Transfer the project with the given ID to the new owner''s workspace' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Transfer the project with the given ID to the new owner's workspace - + diff --git a/apps/docs/content/docs/management-api/endpoints/projects/post-projects.mdx b/apps/docs/content/docs/management-api/endpoints/projects/post-projects.mdx index 7f451cf24c..28cd0e92d0 100644 --- a/apps/docs/content/docs/management-api/endpoints/projects/post-projects.mdx +++ b/apps/docs/content/docs/management-api/endpoints/projects/post-projects.mdx @@ -12,11 +12,11 @@ _openapi: path: /v1/projects url: /management-api/endpoints/projects/post-projects metaTitle: POST /v1/projects | Create project with a postgres database -metaDescription: "Management API: Creates a new project with a postgres database." +metaDescription: 'Management API: Creates a new project with a postgres database.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Creates a new project with a postgres database. - + diff --git a/apps/docs/content/docs/management-api/endpoints/regions/get-regions.mdx b/apps/docs/content/docs/management-api/endpoints/regions/get-regions.mdx index f4fb3615cd..afb8a502e3 100644 --- a/apps/docs/content/docs/management-api/endpoints/regions/get-regions.mdx +++ b/apps/docs/content/docs/management-api/endpoints/regions/get-regions.mdx @@ -12,11 +12,11 @@ _openapi: path: /v1/regions url: /management-api/endpoints/regions/get-regions metaTitle: GET /v1/regions | Get all regions -metaDescription: "Management API: Returns all available regions across products. Optionally filter by product." +metaDescription: 'Management API: Returns all available regions across products. Optionally filter by product.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Returns all available regions across products. Optionally filter by product. - + diff --git a/apps/docs/content/docs/management-api/endpoints/workspaces/get-workspaces-by-id.mdx b/apps/docs/content/docs/management-api/endpoints/workspaces/get-workspaces-by-id.mdx index 64e14dc889..41a8b9faaa 100644 --- a/apps/docs/content/docs/management-api/endpoints/workspaces/get-workspaces-by-id.mdx +++ b/apps/docs/content/docs/management-api/endpoints/workspaces/get-workspaces-by-id.mdx @@ -9,17 +9,14 @@ _openapi: headings: [] contents: - content: Returns the workspace with the given ID. - path: "/v1/workspaces/{id}" + path: '/v1/workspaces/{id}' url: /management-api/endpoints/workspaces/get-workspaces-by-id -metaTitle: "GET /v1/workspaces/{id} | Get workspace" -metaDescription: "Management API: Returns the workspace with the given ID." +metaTitle: 'GET /v1/workspaces/{id} | Get workspace' +metaDescription: 'Management API: Returns the workspace with the given ID.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Returns the workspace with the given ID. - + diff --git a/apps/docs/content/docs/management-api/endpoints/workspaces/get-workspaces.mdx b/apps/docs/content/docs/management-api/endpoints/workspaces/get-workspaces.mdx index f486fc77a4..45b0590237 100644 --- a/apps/docs/content/docs/management-api/endpoints/workspaces/get-workspaces.mdx +++ b/apps/docs/content/docs/management-api/endpoints/workspaces/get-workspaces.mdx @@ -12,11 +12,11 @@ _openapi: path: /v1/workspaces url: /management-api/endpoints/workspaces/get-workspaces metaTitle: GET /v1/workspaces | Get list of workspaces -metaDescription: "Management API: Returns the list of workspaces the current token can access." +metaDescription: 'Management API: Returns the list of workspaces the current token can access.' --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Returns the list of workspaces the current token can access. - + diff --git a/apps/docs/content/docs/management-api/index.mdx b/apps/docs/content/docs/management-api/index.mdx index 38ca995f25..685f86eca0 100644 --- a/apps/docs/content/docs/management-api/index.mdx +++ b/apps/docs/content/docs/management-api/index.mdx @@ -1,8 +1,8 @@ --- title: Management API -description: "Programmatically manage your Prisma Postgres databases, projects, and workspaces with the Management API" +description: 'Programmatically manage your Prisma Postgres databases, projects, and workspaces with the Management API' url: /management-api -metaTitle: "Prisma Postgres: Management API Reference" +metaTitle: 'Prisma Postgres: Management API Reference' metaDescription: Management API reference documentation for Prisma Postgres. --- diff --git a/apps/docs/content/docs/management-api/partner-integration.mdx b/apps/docs/content/docs/management-api/partner-integration.mdx index 698f0e960f..2bfa0c9596 100644 --- a/apps/docs/content/docs/management-api/partner-integration.mdx +++ b/apps/docs/content/docs/management-api/partner-integration.mdx @@ -2,7 +2,7 @@ title: Partner Integration description: Build partner integrations that provision and transfer Prisma Postgres databases to users metaTitle: Partner Integration | Provision & Transfer Databases -metaDescription: "Build partner integrations with the Management API. Provision Prisma Postgres databases, implement claim flow with OAuth 2.0, transfer projects to user workspaces." +metaDescription: 'Build partner integrations with the Management API. Provision Prisma Postgres databases, implement claim flow with OAuth 2.0, transfer projects to user workspaces.' url: /management-api/partner-integration --- diff --git a/apps/docs/content/docs/management-api/sdk.mdx b/apps/docs/content/docs/management-api/sdk.mdx index e540b6b941..47561a1b93 100644 --- a/apps/docs/content/docs/management-api/sdk.mdx +++ b/apps/docs/content/docs/management-api/sdk.mdx @@ -1,9 +1,9 @@ --- title: SDK -description: "A TypeScript SDK for the Prisma Data Platform Management API. Use the simple client for direct API access, or the full SDK with built-in OAuth authentication and automatic token refresh" +description: 'A TypeScript SDK for the Prisma Data Platform Management API. Use the simple client for direct API access, or the full SDK with built-in OAuth authentication and automatic token refresh' url: /management-api/sdk -metaTitle: "Prisma Postgres: Management API SDK" -metaDescription: "A TypeScript SDK for the Prisma Data Platform Management API. Use the simple client for direct API access, or the full SDK with built-in OAuth authentication and automatic token refresh." +metaTitle: 'Prisma Postgres: Management API SDK' +metaDescription: 'A TypeScript SDK for the Prisma Data Platform Management API. Use the simple client for direct API access, or the full SDK with built-in OAuth authentication and automatic token refresh.' --- ## Overview @@ -264,7 +264,6 @@ const tokenStorage: TokenStorage = { ``` For other environments: - - **VS Code extensions** - Use `context.secrets` for secure storage - **Stateless web servers** - Store PKCE state/verifier in encrypted cookies or a database diff --git a/apps/docs/content/docs/orm/core-concepts/api-patterns.mdx b/apps/docs/content/docs/orm/core-concepts/api-patterns.mdx index 843e3b8450..3b368523a7 100644 --- a/apps/docs/content/docs/orm/core-concepts/api-patterns.mdx +++ b/apps/docs/content/docs/orm/core-concepts/api-patterns.mdx @@ -1,6 +1,6 @@ --- title: API patterns -description: "How to use Prisma ORM with REST APIs, GraphQL servers, and fullstack frameworks" +description: 'How to use Prisma ORM with REST APIs, GraphQL servers, and fullstack frameworks' url: /orm/core-concepts/api-patterns metaTitle: Building REST APIs with Prisma ORM metaDescription: This page gives an overview of the most important things when building REST APIs with Prisma. It shows practical examples and the supported libraries. diff --git a/apps/docs/content/docs/orm/core-concepts/supported-databases/index.mdx b/apps/docs/content/docs/orm/core-concepts/supported-databases/index.mdx index bf5ca772b9..b586b427db 100644 --- a/apps/docs/content/docs/orm/core-concepts/supported-databases/index.mdx +++ b/apps/docs/content/docs/orm/core-concepts/supported-databases/index.mdx @@ -1,6 +1,6 @@ --- title: Overview -description: "Prisma ORM supports PostgreSQL, MySQL, SQLite, MongoDB, SQL Server, CockroachDB, and serverless databases" +description: 'Prisma ORM supports PostgreSQL, MySQL, SQLite, MongoDB, SQL Server, CockroachDB, and serverless databases' url: /orm/core-concepts/supported-databases metaTitle: Databases metaDescription: Prisma ORM supports PostgreSQL, MySQL, SQLite, MongoDB, SQL Server, CockroachDB, and serverless databases' @@ -10,14 +10,14 @@ metaDescription: Prisma ORM supports PostgreSQL, MySQL, SQLite, MongoDB, SQL Ser ### Self-hosted -| Database | Version | -| ---------------------------------------------------------------- | ------- | +| Database | Version | +| ----------- | ------- | | [PostgreSQL](/orm/core-concepts/supported-databases/postgresql) | 9.6+ | -| [MySQL](/orm/core-concepts/supported-databases/mysql) | 5.6+ | -| [MariaDB](/orm/core-concepts/supported-databases/mysql) | 10.0+ | +| [MySQL](/orm/core-concepts/supported-databases/mysql) | 5.6+ | +| [MariaDB](/orm/core-concepts/supported-databases/mysql) | 10.0+ | | [SQL Server](/orm/core-concepts/supported-databases/sql-server) | 2017+ | -| [SQLite](/orm/core-concepts/supported-databases/sqlite) | All | -| [MongoDB](/orm/core-concepts/supported-databases/mongodb) | 4.2+ | +| [SQLite](/orm/core-concepts/supported-databases/sqlite) | All | +| [MongoDB](/orm/core-concepts/supported-databases/mongodb) | 4.2+ | | [CockroachDB](/orm/core-concepts/supported-databases/postgresql) | 21.2.4+ | ### Managed/Serverless diff --git a/apps/docs/content/docs/orm/core-concepts/supported-databases/mysql.mdx b/apps/docs/content/docs/orm/core-concepts/supported-databases/mysql.mdx index bf24514d8d..66da6a59d6 100644 --- a/apps/docs/content/docs/orm/core-concepts/supported-databases/mysql.mdx +++ b/apps/docs/content/docs/orm/core-concepts/supported-databases/mysql.mdx @@ -97,12 +97,12 @@ Standard MySQL (5.6+) or MariaDB (10.0+) servers. **Connection string arguments:** -| Argument | Default | Description | -| ----------------- | ---------------------- | ------------------------------ | -| `connect_timeout` | `5` | Seconds to wait for connection | -| `sslcert` | | Path to server certificate | -| `sslidentity` | | Path to PKCS12 certificate | -| `sslaccept` | `accept_invalid_certs` | Certificate validation mode | +| Argument | Default | Description | +|----------|---------|-------------| +| `connect_timeout` | `5` | Seconds to wait for connection | +| `sslcert` | | Path to server certificate | +| `sslidentity` | | Path to PKCS12 certificate | +| `sslaccept` | `accept_invalid_certs` | Certificate validation mode | ### PlanetScale @@ -114,14 +114,12 @@ Serverless MySQL-compatible database built on Vitess clustering system. - Non-blocking schema changes **Key features:** - - Enterprise scalability across multiple servers - Database branches for schema testing - Non-blocking schema deployments - Serverless-optimized (avoids connection limits) **Branch workflow:** - 1. **Development branches** - Test schema changes freely 2. **Production branches** - Protected, require deploy requests 3. **Deploy requests** - Merge dev changes to production @@ -177,17 +175,17 @@ model Comment { ### Type mapping between MySQL and Prisma schema -| Prisma | MySQL/MariaDB | -| ---------- | ---------------- | -| `String` | `VARCHAR(191)` | -| `Boolean` | `TINYINT(1)` | -| `Int` | `INT` | -| `BigInt` | `BIGINT` | -| `Float` | `DOUBLE` | -| `Decimal` | `DECIMAL(65,30)` | -| `DateTime` | `DATETIME(3)` | -| `Json` | `JSON` | -| `Bytes` | `LONGBLOB` | +| Prisma | MySQL/MariaDB | +|--------|---------------| +| `String` | `VARCHAR(191)` | +| `Boolean` | `TINYINT(1)` | +| `Int` | `INT` | +| `BigInt` | `BIGINT` | +| `Float` | `DOUBLE` | +| `Decimal` | `DECIMAL(65,30)` | +| `DateTime` | `DATETIME(3)` | +| `Json` | `JSON` | +| `Bytes` | `LONGBLOB` | See [full type mapping reference](/orm/reference/prisma-schema-reference#model-field-scalar-types) for complete details. @@ -225,7 +223,6 @@ model User { **Connection troubleshooting:** PlanetScale production branches are read-only for direct DDL. If you get error P3022, ensure you're: - - Using `prisma db push` instead of `prisma migrate` - Working on a development branch, or - Using a deploy request to update production diff --git a/apps/docs/content/docs/orm/core-concepts/supported-databases/postgresql.mdx b/apps/docs/content/docs/orm/core-concepts/supported-databases/postgresql.mdx index 9d08c4a97e..27cc79eb53 100644 --- a/apps/docs/content/docs/orm/core-concepts/supported-databases/postgresql.mdx +++ b/apps/docs/content/docs/orm/core-concepts/supported-databases/postgresql.mdx @@ -1,6 +1,6 @@ --- title: PostgreSQL -description: "Use Prisma ORM with PostgreSQL databases including self-hosted, serverless (Neon, Supabase), and CockroachDB" +description: 'Use Prisma ORM with PostgreSQL databases including self-hosted, serverless (Neon, Supabase), and CockroachDB' url: /orm/core-concepts/supported-databases/postgresql metaTitle: PostgreSQL database connector metaDescription: This page explains how Prisma can connect to a PostgreSQL database using the PostgreSQL database connector. @@ -96,13 +96,13 @@ Standard PostgreSQL server (9.6+). **Connection string arguments:** -| Argument | Default | Description | -| ----------------- | -------- | ----------------------------------------------- | -| `schema` | `public` | PostgreSQL schema to use | -| `connect_timeout` | `5` | Seconds to wait for connection (0 = no timeout) | -| `sslmode` | `prefer` | TLS mode: `prefer`, `disable`, `require` | -| `sslcert` | | Path to server certificate | -| `sslidentity` | | Path to PKCS12 certificate | +| Argument | Default | Description | +|----------|---------|-------------| +| `schema` | `public` | PostgreSQL schema to use | +| `connect_timeout` | `5` | Seconds to wait for connection (0 = no timeout) | +| `sslmode` | `prefer` | TLS mode: `prefer`, `disable`, `require` | +| `sslcert` | | Path to server certificate | +| `sslidentity` | | Path to PKCS12 certificate | ### Neon @@ -123,13 +123,11 @@ Serverless PostgreSQL with automatic scaling and branching. PostgreSQL hosting with built-in auth, storage, and real-time features. **Connection types:** - - **Direct:** `db.[project-ref].supabase.co:5432` - **Transaction pooler:** Port `6543` with `?pgbouncer=true` - **Session pooler:** Port `5432` on pooler host **Key features:** - - Supavisor connection pooling - Built-in PostgreSQL extensions - Integrated with Supabase ecosystem @@ -148,11 +146,11 @@ Distributed, PostgreSQL-compatible database designed for scalability and high av **Key differences:** -| Feature | PostgreSQL | CockroachDB | -| -------------- | ----------------- | ------------------------------------- | -| Native types | `VARCHAR(n)` | `STRING(n)` | -| ID generation | `autoincrement()` | Uses `unique_rowid()` | -| Sequential IDs | Recommended | Avoid (use `autoincrement()` instead) | +| Feature | PostgreSQL | CockroachDB | +|---------|-----------|-------------| +| Native types | `VARCHAR(n)` | `STRING(n)` | +| ID generation | `autoincrement()` | Uses `unique_rowid()` | +| Sequential IDs | Recommended | Avoid (use `autoincrement()` instead) | **ID generation example:** @@ -178,17 +176,17 @@ model User { ### Prisma to PostgreSQL -| Prisma | PostgreSQL | CockroachDB | -| ---------- | ------------------ | ----------- | -| `String` | `text` | `STRING` | -| `Boolean` | `boolean` | `BOOL` | -| `Int` | `integer` | `INT4` | -| `BigInt` | `bigint` | `INT8` | -| `Float` | `double precision` | `FLOAT8` | -| `Decimal` | `decimal(65,30)` | `DECIMAL` | -| `DateTime` | `timestamp(3)` | `TIMESTAMP` | -| `Json` | `jsonb` | `JSONB` | -| `Bytes` | `bytea` | `BYTES` | +| Prisma | PostgreSQL | CockroachDB | +|--------|-----------|-------------| +| `String` | `text` | `STRING` | +| `Boolean` | `boolean` | `BOOL` | +| `Int` | `integer` | `INT4` | +| `BigInt` | `bigint` | `INT8` | +| `Float` | `double precision` | `FLOAT8` | +| `Decimal` | `decimal(65,30)` | `DECIMAL` | +| `DateTime` | `timestamp(3)` | `TIMESTAMP` | +| `Json` | `jsonb` | `JSONB` | +| `Bytes` | `bytea` | `BYTES` | See [full type mapping reference](/orm/reference/prisma-schema-reference#model-field-scalar-types) for complete details. @@ -215,14 +213,13 @@ DATABASE_URL="postgresql://user:pass@localhost/db?host=/var/run/postgresql/" ```ts const adapter = new PrismaPg( { connectionString: process.env.DATABASE_URL }, - { schema: "mySchema" }, + { schema: "mySchema" } ); ``` **Connection pool defaults (Prisma ORM v7):** Driver adapters use `pg` defaults which differ from v6: - - **Connection timeout:** `0` (no timeout) vs v6's `5s` - **Idle timeout:** `10s` vs v6's `300s` diff --git a/apps/docs/content/docs/orm/core-concepts/supported-databases/sql-server.mdx b/apps/docs/content/docs/orm/core-concepts/supported-databases/sql-server.mdx index d40a5406e3..c9c3c29d2a 100644 --- a/apps/docs/content/docs/orm/core-concepts/supported-databases/sql-server.mdx +++ b/apps/docs/content/docs/orm/core-concepts/supported-databases/sql-server.mdx @@ -77,25 +77,24 @@ sqlserver://host:1433;user={MyServer/User};password={Pass:Word;};database=db ### Connection string arguments -| Argument | Default | Description | -| ------------------------------ | -------- | -------------------------------------------------------------------- | -| `database` / `initial catalog` | `master` | Database name | -| `user` / `username` / `uid` | | SQL Server login or Windows username (if using `integratedSecurity`) | -| `password` / `pwd` | | Password for user | -| `encrypt` | `true` | Use TLS: `true` (always), `false` (login only) | -| `integratedSecurity` | | Windows authentication: `true`, `false`, `yes`, `no` | -| `schema` | `dbo` | Schema prefix for all queries | -| `connectTimeout` | `5` | Seconds to wait for connection | -| `socketTimeout` | | Seconds to wait for each query | -| `poolTimeout` | `10` | Seconds to wait for connection from pool | -| `trustServerCertificate` | `false` | Trust server certificate without validation | -| `trustServerCertificateCA` | | Path to CA certificate file (`.pem`, `.crt`, `.der`) | -| `ApplicationName` | | Application name for the connection | +| Argument | Default | Description | +|----------|---------|-------------| +| `database` / `initial catalog` | `master` | Database name | +| `user` / `username` / `uid` | | SQL Server login or Windows username (if using `integratedSecurity`) | +| `password` / `pwd` | | Password for user | +| `encrypt` | `true` | Use TLS: `true` (always), `false` (login only) | +| `integratedSecurity` | | Windows authentication: `true`, `false`, `yes`, `no` | +| `schema` | `dbo` | Schema prefix for all queries | +| `connectTimeout` | `5` | Seconds to wait for connection | +| `socketTimeout` | | Seconds to wait for each query | +| `poolTimeout` | `10` | Seconds to wait for connection from pool | +| `trustServerCertificate` | `false` | Trust server certificate without validation | +| `trustServerCertificateCA` | | Path to CA certificate file (`.pem`, `.crt`, `.der`) | +| `ApplicationName` | | Application name for the connection | :::warning[Prisma ORM v7: Connection pool defaults changed] Driver adapters use `mssql` driver defaults which differ from v6: - - **Connection timeout:** `15s` (vs v6's `5s`) - **Idle timeout:** `30s` (vs v6's `300s`) @@ -125,17 +124,17 @@ sqlserver://mycomputer\sql2019;database=sample;integratedSecurity=true;trustServ ## Type mappings -| Prisma | SQL Server | -| ---------- | ---------------- | -| `String` | `NVARCHAR(1000)` | -| `Boolean` | `BIT` | -| `Int` | `INT` | -| `BigInt` | `BIGINT` | -| `Float` | `FLOAT(53)` | -| `Decimal` | `DECIMAL(32,16)` | -| `DateTime` | `DATETIME2` | -| `Json` | Not supported | -| `Bytes` | `VARBINARY(MAX)` | +| Prisma | SQL Server | +|--------|------------| +| `String` | `NVARCHAR(1000)` | +| `Boolean` | `BIT` | +| `Int` | `INT` | +| `BigInt` | `BIGINT` | +| `Float` | `FLOAT(53)` | +| `Decimal` | `DECIMAL(32,16)` | +| `DateTime` | `DATETIME2` | +| `Json` | Not supported | +| `Bytes` | `VARBINARY(MAX)` | See [full type mapping reference](/orm/reference/prisma-schema-reference#model-field-scalar-types) for complete details. @@ -174,7 +173,6 @@ sqlserver://host:1433;database=db;schema=dbo;... **Destructive changes:** Some operations require table recreation: - - Adding/removing `autoincrement()` - Dropping all columns from a table diff --git a/apps/docs/content/docs/orm/core-concepts/supported-databases/sqlite.mdx b/apps/docs/content/docs/orm/core-concepts/supported-databases/sqlite.mdx index af10244658..216ab34b2e 100644 --- a/apps/docs/content/docs/orm/core-concepts/supported-databases/sqlite.mdx +++ b/apps/docs/content/docs/orm/core-concepts/supported-databases/sqlite.mdx @@ -1,6 +1,6 @@ --- title: SQLite -description: "Use Prisma ORM with SQLite databases including local SQLite, Turso (libSQL), and Cloudflare D1" +description: 'Use Prisma ORM with SQLite databases including local SQLite, Turso (libSQL), and Cloudflare D1' url: /orm/core-concepts/supported-databases/sqlite metaTitle: SQLite database connector metaDescription: This page explains how Prisma can connect to a SQLite database using the SQLite database connector. @@ -100,7 +100,6 @@ Edge-hosted, distributed SQLite-compatible database. - Use local SQLite file + Turso CLI for migrations (see [Turso docs](https://docs.turso.tech/)) **Key differences:** - - Remote access over HTTP - Replication and automated backups - Schema changes via `prisma migrate diff` + Turso CLI @@ -114,7 +113,6 @@ Serverless SQLite database for Cloudflare Workers. - Local (`.wrangler/state`) and remote versions available **Key differences:** - - No transaction support currently - Migrations via Wrangler: `wrangler d1 migrations apply` - Deploy with Cloudflare Workers @@ -149,7 +147,7 @@ Configure how `DateTime` values are stored: ```ts const adapter = new PrismaBetterSqlite3( { url: "file:./dev.db" }, - { timestampFormat: "unixepoch-ms" }, // For backward compatibility + { timestampFormat: "unixepoch-ms" } // For backward compatibility ); ``` diff --git a/apps/docs/content/docs/orm/index.mdx b/apps/docs/content/docs/orm/index.mdx index 8e69ed5f94..9ec833cb03 100644 --- a/apps/docs/content/docs/orm/index.mdx +++ b/apps/docs/content/docs/orm/index.mdx @@ -1,6 +1,6 @@ --- title: Prisma ORM -description: "Prisma ORM is a next-generation Node.js and TypeScript ORM that provides type-safe database access, migrations, and a visual data editor." +description: 'Prisma ORM is a next-generation Node.js and TypeScript ORM that provides type-safe database access, migrations, and a visual data editor.' url: /orm metaTitle: What is Prisma ORM? (Overview) metaDescription: This page gives a high-level overview of what Prisma ORM is and how it works. It's a great starting point for Prisma newcomers @@ -29,14 +29,12 @@ Prisma takes a different approach: ## When to use Prisma **Prisma is a good fit if you:** - - Build server-side applications (REST, GraphQL, gRPC, serverless) - Value type safety and developer experience - Work in a team and want a clear, declarative schema - Need migrations, querying, and data modeling in one toolkit **Consider alternatives if you:** - - Need full control over every SQL query (use raw SQL drivers) - Want a no-code backend (use a BaaS like Supabase or Firebase) - Need an auto-generated CRUD GraphQL API (use Hasura or PostGraphile) diff --git a/apps/docs/content/docs/orm/more/best-practices.mdx b/apps/docs/content/docs/orm/more/best-practices.mdx index c2e70809c6..f2ad4a0b9f 100644 --- a/apps/docs/content/docs/orm/more/best-practices.mdx +++ b/apps/docs/content/docs/orm/more/best-practices.mdx @@ -1,8 +1,8 @@ --- title: Best practices -description: "Learn production-ready patterns for schema design, query optimization, type safety, security, and deployment with Prisma ORM." +description: 'Learn production-ready patterns for schema design, query optimization, type safety, security, and deployment with Prisma ORM.' metaTitle: Best practices -metaDescription: "Learn production-ready patterns for schema design, query optimization, type safety, security, and deployment with Prisma ORM." +metaDescription: 'Learn production-ready patterns for schema design, query optimization, type safety, security, and deployment with Prisma ORM.' url: /orm/more/best-practices --- @@ -52,7 +52,6 @@ model Comment { @@index([postId]) } ``` - ::: ### Index strategy @@ -113,14 +112,14 @@ The `schema.prisma` file (containing the `generator` block) and `migrations/` di Create one global `PrismaClient` instance and reuse it throughout your application. Creating multiple instances creates multiple connection pools, which can exhaust your database's connection limit and slow down queries. ```ts title="lib/prisma.ts" -import { PrismaClient } from "../generated/prisma/client"; -import { PrismaPg } from "@prisma/adapter-pg"; +import { PrismaClient } from '../generated/prisma/client' +import { PrismaPg } from '@prisma/adapter-pg' const adapter = new PrismaPg({ - connectionString: process.env.DATABASE_URL, -}); + connectionString: process.env.DATABASE_URL +}) -export const prisma = new PrismaClient({ adapter }); +export const prisma = new PrismaClient({ adapter }) ``` **Serverless environments:** @@ -134,23 +133,23 @@ The N+1 problem occurs when you run 1 query to fetch a list, then 1 additional q ```ts title="n-plus-one.ts" // ❌ Bad: N+1 queries (1 + N queries) -const users = await prisma.user.findMany(); +const users = await prisma.user.findMany() for (const user of users) { const posts = await prisma.post.findMany({ - where: { authorId: user.id }, - }); + where: { authorId: user.id } + }) } // ✅ Good: Single query with include const users = await prisma.user.findMany({ - include: { posts: true }, -}); + include: { posts: true } +}) // ✅ Good: Batch with IN filter -const users = await prisma.user.findMany(); +const users = await prisma.user.findMany() const posts = await prisma.post.findMany({ - where: { authorId: { in: users.map((u) => u.id) } }, -}); + where: { authorId: { in: users.map(u => u.id) } } +}) ``` ### Select only needed fields @@ -162,27 +161,27 @@ const user = await prisma.user.findFirst({ select: { id: true, email: true, - role: true, - }, -}); + role: true + } +}) ``` Use `omit` to blacklist fields you want excluded (useful for sensitive data): ```ts title="omit.ts" -import { PrismaClient } from "../generated/prisma/client"; -import { PrismaPg } from "@prisma/adapter-pg"; +import { PrismaClient } from '../generated/prisma/client' +import { PrismaPg } from '@prisma/adapter-pg' const adapter = new PrismaPg({ - connectionString: process.env.DATABASE_URL, -}); + connectionString: process.env.DATABASE_URL +}) const prisma = new PrismaClient({ adapter, omit: { - user: { secretValue: true }, - }, -}); + user: { secretValue: true } + } +}) ``` You cannot combine `select` and `omit` in the same query. @@ -195,8 +194,8 @@ Use **offset pagination** for small datasets where jumping to arbitrary pages is const posts = await prisma.post.findMany({ skip: 40, take: 10, - where: { email: { contains: "prisma.io" } }, -}); + where: { email: { contains: 'prisma.io' } }, +}) ``` Use **cursor-based pagination** for large datasets or infinite scroll. Cursor-based pagination scales better because it uses indexed columns to find the starting position instead of traversing skipped rows: @@ -209,9 +208,9 @@ const posts = await prisma.post.findMany({ id: lastPost.id, }, orderBy: { - id: "asc", + id: 'asc', }, -}); +}) ``` ### Batch operations @@ -220,13 +219,16 @@ Use bulk methods when operating on multiple records: ```ts title="batch-operations.ts" await prisma.user.createMany({ - data: [{ email: "alice@prisma.io" }, { email: "bob@prisma.io" }], -}); + data: [ + { email: 'alice@prisma.io' }, + { email: 'bob@prisma.io' } + ] +}) await prisma.post.updateMany({ where: { published: false }, - data: { published: true }, -}); + data: { published: true } +}) ``` Bulk operations (`createMany`, `createManyAndReturn`, `updateMany`, `updateManyAndReturn`, and `deleteMany`) [automatically run as transactions](/orm/prisma-client/queries/transactions#batch-operations), so all writes either succeed together or are rolled back if something fails. @@ -236,10 +238,10 @@ Bulk operations (`createMany`, `createManyAndReturn`, `updateMany`, `updateManyA Prefer Prisma ORM's query API. Use raw SQL only when you need features not supported by Prisma ORM or heavily optimized queries: ```ts title="raw-query.ts" -const email = "user@example.com"; +const email = 'user@example.com' const users = await prisma.$queryRaw` SELECT * FROM "User" WHERE email = ${email} -`; +` ``` :::warning @@ -253,14 +255,14 @@ Never concatenate user input into SQL strings. Always use parameterized queries Use Prisma ORM's generated types instead of duplicating interfaces: ```ts title="generated-types.ts" -import type { User } from "../generated/prisma/client"; +import type { User } from '../generated/prisma/client' async function getAdminEmails(): Promise { const admins: User[] = await prisma.user.findMany({ - where: { role: "ADMIN" }, - }); + where: { role: 'ADMIN' } + }) - return admins.map((a) => a.email); + return admins.map(a => a.email) } ``` @@ -269,16 +271,16 @@ async function getAdminEmails(): Promise { Always validate and sanitize user input before database operations: ```ts title="input-validation.ts" -import { z } from "zod"; +import { z } from 'zod' const createUserSchema = z.object({ email: z.string().email(), - name: z.string().min(1).max(100), -}); + name: z.string().min(1).max(100) +}) async function createUser(input: unknown) { - const data = createUserSchema.parse(input); - return prisma.user.create({ data }); + const data = createUserSchema.parse(input) + return prisma.user.create({ data }) } ``` @@ -292,14 +294,17 @@ Prisma ORM's API is safe by default. For raw queries, always use parameterized q // ✅ Safe: tagged template const result = await prisma.$queryRaw` SELECT * FROM "User" WHERE email = ${email} -`; +` // ✅ Safe: parameterized -const result = await prisma.$queryRawUnsafe('SELECT * FROM "User" WHERE email = $1', email); +const result = await prisma.$queryRawUnsafe( + 'SELECT * FROM "User" WHERE email = $1', + email +) // ❌ Unsafe: string concatenation -const query = `SELECT * FROM "User" WHERE email = '${email}'`; -const result = await prisma.$queryRawUnsafe(query); +const query = `SELECT * FROM "User" WHERE email = '${email}'` +const result = await prisma.$queryRawUnsafe(query) ``` ### Sensitive data handling @@ -307,26 +312,26 @@ const result = await prisma.$queryRawUnsafe(query); Exclude sensitive fields from query results: ```ts title="sensitive-data.ts" -import { PrismaClient } from "../generated/prisma/client"; -import { PrismaPg } from "@prisma/adapter-pg"; +import { PrismaClient } from '../generated/prisma/client' +import { PrismaPg } from '@prisma/adapter-pg' const adapter = new PrismaPg({ - connectionString: process.env.DATABASE_URL, -}); + connectionString: process.env.DATABASE_URL +}) // Global exclusion const prisma = new PrismaClient({ adapter, omit: { - user: { secretValue: true }, - }, -}); + user: { secretValue: true } + } +}) // Per-query exclusion const user = await prisma.user.findUnique({ where: { id: 1 }, - omit: { secretValue: true }, -}); + omit: { secretValue: true } +}) ``` ## Testing @@ -346,24 +351,24 @@ Use a dedicated test database that can be reset freely: Mock Prisma ORM using `jest-mock-extended`: ```ts title="unit-test.ts" -import { PrismaClient } from "../generated/prisma/client"; -import { mockDeep } from "jest-mock-extended"; +import { PrismaClient } from '../generated/prisma/client' +import { mockDeep } from 'jest-mock-extended' -const prismaMock = mockDeep(); +const prismaMock = mockDeep() -test("finds user by email", async () => { +test('finds user by email', async () => { prismaMock.user.findUnique.mockResolvedValue({ id: 1, - email: "test@example.com", - name: "Test User", - }); + email: 'test@example.com', + name: 'Test User' + }) const user = await prismaMock.user.findUnique({ - where: { email: "test@example.com" }, - }); + where: { email: 'test@example.com' } + }) - expect(user).toBeDefined(); -}); + expect(user).toBeDefined() +}) ``` ### Integration tests with real database @@ -371,32 +376,32 @@ test("finds user by email", async () => { Use a real database with Prisma Migrate: ```ts title="integration-test.ts" -import { PrismaClient } from "../generated/prisma/client"; -import { PrismaPg } from "@prisma/adapter-pg"; +import { PrismaClient } from '../generated/prisma/client' +import { PrismaPg } from '@prisma/adapter-pg' const adapter = new PrismaPg({ - connectionString: process.env.DATABASE_URL, -}); + connectionString: process.env.DATABASE_URL +}) -const prisma = new PrismaClient({ adapter }); +const prisma = new PrismaClient({ adapter }) beforeEach(async () => { await prisma.user.create({ - data: { email: "test@example.com", name: "Test" }, - }); -}); + data: { email: 'test@example.com', name: 'Test' } + }) +}) afterEach(async () => { - await prisma.user.deleteMany(); -}); + await prisma.user.deleteMany() +}) -test("creates user", async () => { +test('creates user', async () => { const user = await prisma.user.create({ - data: { email: "new@example.com", name: "New User" }, - }); + data: { email: 'new@example.com', name: 'New User' } + }) - expect(user.email).toBe("new@example.com"); -}); + expect(user.email).toBe('new@example.com') +}) ``` ## Production deployment @@ -433,21 +438,21 @@ For AWS Lambda, Vercel, Cloudflare Workers, or similar platforms: 3. Consider external connection poolers (like PgBouncer) for high-concurrency workloads ```ts title="serverless-handler.ts" -import { PrismaClient } from "../generated/prisma/client"; -import { PrismaPg } from "@prisma/adapter-pg"; +import { PrismaClient } from '../generated/prisma/client' +import { PrismaPg } from '@prisma/adapter-pg' const adapter = new PrismaPg({ - connectionString: process.env.DATABASE_URL, -}); + connectionString: process.env.DATABASE_URL +}) -const prisma = new PrismaClient({ adapter }); +const prisma = new PrismaClient({ adapter }) export async function handler(event) { - const users = await prisma.user.findMany(); + const users = await prisma.user.findMany() return { statusCode: 200, - body: JSON.stringify(users), - }; + body: JSON.stringify(users) + } } ``` diff --git a/apps/docs/content/docs/orm/more/comparisons/prisma-and-drizzle.mdx b/apps/docs/content/docs/orm/more/comparisons/prisma-and-drizzle.mdx index d7acbf1630..e68a20c950 100644 --- a/apps/docs/content/docs/orm/more/comparisons/prisma-and-drizzle.mdx +++ b/apps/docs/content/docs/orm/more/comparisons/prisma-and-drizzle.mdx @@ -285,6 +285,7 @@ const posts = await db.select().from(posts).where(ilike(posts.title, "%Hello Wor Both Drizzle and Prisma ORM have the ability to log queries and the underlying SQL generated. + These products work hand-in-hand with Prisma ORM to offer comprehensive data tooling, making building data-driven applications easy by following [Data DX](https://www.datadx.io/) principles. ## Safer Changes and Fewer Bugs diff --git a/apps/docs/content/docs/orm/more/comparisons/prisma-and-mongoose.mdx b/apps/docs/content/docs/orm/more/comparisons/prisma-and-mongoose.mdx index 1cf24beb7c..88beabe37d 100644 --- a/apps/docs/content/docs/orm/more/comparisons/prisma-and-mongoose.mdx +++ b/apps/docs/content/docs/orm/more/comparisons/prisma-and-mongoose.mdx @@ -59,7 +59,7 @@ const userWithPost = await prisma.user.findUnique({ include: { post: true, }, -}); +}) ``` ```ts tab="Fluent API" @@ -69,7 +69,7 @@ const userWithPost = await prisma.user id: 2, }, }) - .post(); + .post() ``` **Mongoose** @@ -186,17 +186,17 @@ const user = await prisma.user.create({ ```ts tab="Using create" const user = await User.create({ - name: "Alice", - email: "alice@prisma.io", -}); + name: 'Alice', + email: 'alice@prisma.io', +}) ``` ```ts tab="Using save" const user = new User({ - name: "Alice", - email: "alice@prisma.io", -}); -await user.save(); + name: 'Alice', + email: 'alice@prisma.io', +}) +await user.save() ``` ## Updating objects @@ -221,15 +221,15 @@ const updatedUser = await User.findOneAndUpdate( { _id: 2 }, { $set: { - name: "Alicia", + name: 'Alicia', }, - }, -); + } +) ``` ```ts tab="Using save" -user.name = "Alicia"; -await user.save(); +user.name = 'Alicia' +await user.save() ``` ## Deleting objects diff --git a/apps/docs/content/docs/orm/more/comparisons/prisma-and-sequelize.mdx b/apps/docs/content/docs/orm/more/comparisons/prisma-and-sequelize.mdx index 9801b352f2..d8aef4c764 100644 --- a/apps/docs/content/docs/orm/more/comparisons/prisma-and-sequelize.mdx +++ b/apps/docs/content/docs/orm/more/comparisons/prisma-and-sequelize.mdx @@ -239,17 +239,17 @@ const user = await prisma.user.create({ ```ts tab="Using save" const user = User.build({ - name: "Alice", - email: "alice@prisma,io", -}); -await user.save(); + name: 'Alice', + email: 'alice@prisma,io', +}) +await user.save() ``` ```ts tab="Using create" const user = await User.create({ - name: "Alice", - email: "alice@prisma,io", -}); + name: 'Alice', + email: 'alice@prisma,io', +}) ``` ### Updating objects @@ -270,16 +270,16 @@ const user = await prisma.user.update({ **Sequelize** ```ts tab="Using save" -user.name = "James"; -user.email = " alice@prisma.com"; -await user.save(); +user.name = 'James' +user.email = ' alice@prisma.com' +await user.save() ``` ```ts tab="Using update" await User.update({ - name: "James", - email: "james@prisma.io", -}); + name: 'James', + email: 'james@prisma.io', +}) ``` ### Deleting objects @@ -378,49 +378,49 @@ const user = await prisma.user.create({ return sequelize.$transaction(async (t) => { const user = await User.create( { - name: "Alice", - email: "alice@prisma,io", + name: 'Alice', + email: 'alice@prisma,io', }, { transaction: t, - }, - ); + } + ) const post1 = await Post.create( { - title: "Join us for GraphQL Conf in 2019", + title: 'Join us for GraphQL Conf in 2019', }, { transaction: t, - }, - ); + } + ) const post2 = await Post.create( { - title: "Subscribe to GraphQL Weekly for GraphQL news", + title: 'Subscribe to GraphQL Weekly for GraphQL news', }, { transaction: t, - }, - ); - await user.setPosts([post1, post2]); -}); + } + ) + await user.setPosts([post1, post2]) +}) ``` ```ts tab="Automatic" return sequelize.$transaction(async (transaction) => { try { const user = await User.create({ - name: "Alice", - email: "alice@prisma,io", - }); + name: 'Alice', + email: 'alice@prisma,io', + }) const post1 = await Post.create({ - title: "Join us for GraphQL Conf in 2019", - }); + title: 'Join us for GraphQL Conf in 2019', + }) const post2 = await Post.create({ - title: "Subscribe to GraphQL Weekly for GraphQL news", - }); - await user.setPosts([post1, post2]); + title: 'Subscribe to GraphQL Weekly for GraphQL news', + }) + await user.setPosts([post1, post2]) } catch (e) { - return transaction.rollback(); + return transaction.rollback() } -}); +}) ``` diff --git a/apps/docs/content/docs/orm/more/releases.mdx b/apps/docs/content/docs/orm/more/releases.mdx index 6e79894b5a..ff0b6c5fdc 100644 --- a/apps/docs/content/docs/orm/more/releases.mdx +++ b/apps/docs/content/docs/orm/more/releases.mdx @@ -1,9 +1,9 @@ --- title: ORM releases and maturity levels -description: "Learn about the release process, versioning, and maturity of Prisma ORM components and how to deal with breaking changes that might happen throughout releases" +description: 'Learn about the release process, versioning, and maturity of Prisma ORM components and how to deal with breaking changes that might happen throughout releases' url: /orm/more/releases metaTitle: ORM releases and maturity levels -metaDescription: "Learn about the release process, versioning, and maturity of Prisma ORM components and how to deal with breaking changes that might happen throughout releases." +metaDescription: 'Learn about the release process, versioning, and maturity of Prisma ORM components and how to deal with breaking changes that might happen throughout releases.' --- This page explains the release process of Prisma ORM, how it's versioned and how to deal with breaking changes that might happen throughout releases. diff --git a/apps/docs/content/docs/orm/more/troubleshooting/many-to-many-relations.mdx b/apps/docs/content/docs/orm/more/troubleshooting/many-to-many-relations.mdx index 6caac4d1a7..53925162aa 100644 --- a/apps/docs/content/docs/orm/more/troubleshooting/many-to-many-relations.mdx +++ b/apps/docs/content/docs/orm/more/troubleshooting/many-to-many-relations.mdx @@ -1,6 +1,6 @@ --- title: Many-to-many relations -description: "Learn how to model, query, and convert many-to-many relations with Prisma ORM" +description: 'Learn how to model, query, and convert many-to-many relations with Prisma ORM' url: /orm/more/troubleshooting/many-to-many-relations metaTitle: Modeling and querying many-to-many relations metaDescription: Learn how you can model and query implicit and explicit many-to-many relations with Prisma ORM diff --git a/apps/docs/content/docs/orm/more/troubleshooting/nextjs.mdx b/apps/docs/content/docs/orm/more/troubleshooting/nextjs.mdx index fa1b611853..ca3d99fe20 100644 --- a/apps/docs/content/docs/orm/more/troubleshooting/nextjs.mdx +++ b/apps/docs/content/docs/orm/more/troubleshooting/nextjs.mdx @@ -2,7 +2,7 @@ title: Next.js description: Best practices and troubleshooting for using Prisma ORM with Next.js applications url: /orm/more/troubleshooting/nextjs -metaDescription: "Learn best practices, monorepo strategies, and dynamic usage techniques for Prisma ORM in Next.js applications." +metaDescription: 'Learn best practices, monorepo strategies, and dynamic usage techniques for Prisma ORM in Next.js applications.' metaTitle: Comprehensive Guide to Using Prisma ORM with Next.js --- diff --git a/apps/docs/content/docs/orm/more/troubleshooting/nuxt.mdx b/apps/docs/content/docs/orm/more/troubleshooting/nuxt.mdx index a561ea3884..574b67e105 100644 --- a/apps/docs/content/docs/orm/more/troubleshooting/nuxt.mdx +++ b/apps/docs/content/docs/orm/more/troubleshooting/nuxt.mdx @@ -3,7 +3,7 @@ title: Nuxt description: Learn how to integrate Prisma ORM with your Nuxt application url: /orm/more/troubleshooting/nuxt metaTitle: Add Prisma ORM Easily to Your Nuxt Apps -metaDescription: "Learn how to easily add Prisma ORM to your Nuxt apps, use its features, and understand its limitations." +metaDescription: 'Learn how to easily add Prisma ORM to your Nuxt apps, use its features, and understand its limitations.' --- :::warning diff --git a/apps/docs/content/docs/orm/more/troubleshooting/raw-sql-comparisons.mdx b/apps/docs/content/docs/orm/more/troubleshooting/raw-sql-comparisons.mdx index 5b13cdbc28..9753e0a6c5 100644 --- a/apps/docs/content/docs/orm/more/troubleshooting/raw-sql-comparisons.mdx +++ b/apps/docs/content/docs/orm/more/troubleshooting/raw-sql-comparisons.mdx @@ -50,7 +50,8 @@ const response = ### SQLite ```js -const response = await prisma.$queryRaw`SELECT * FROM "Post" WHERE "likesCount" < "commentsCount";`; +const response = + await prisma.$queryRaw`SELECT * FROM "Post" WHERE "likesCount" < "commentsCount";`; ``` ## Comparing date values @@ -86,5 +87,6 @@ const response = ### SQLite ```js -const response = await prisma.$queryRaw`SELECT * FROM "Project" WHERE "completedDate" > "dueDate";`; +const response = + await prisma.$queryRaw`SELECT * FROM "Project" WHERE "completedDate" > "dueDate";`; ``` diff --git a/apps/docs/content/docs/orm/prisma-client/client-extensions/client.mdx b/apps/docs/content/docs/orm/prisma-client/client-extensions/client.mdx index b126648a7d..d1eedbf0f1 100644 --- a/apps/docs/content/docs/orm/prisma-client/client-extensions/client.mdx +++ b/apps/docs/content/docs/orm/prisma-client/client-extensions/client.mdx @@ -1,9 +1,9 @@ --- title: Add methods to Prisma Client -description: "Extend the functionality of Prisma Client, client component" +description: 'Extend the functionality of Prisma Client, client component' url: /orm/prisma-client/client-extensions/client -metaTitle: "Prisma Client extensions: client component" -metaDescription: "Extend the functionality of Prisma Client, client component" +metaTitle: 'Prisma Client extensions: client component' +metaDescription: 'Extend the functionality of Prisma Client, client component' --- You can use the `client` [Prisma Client extensions](/orm/prisma-client/client-extensions) component to add top-level methods to Prisma Client. @@ -27,6 +27,7 @@ The following example uses the `client` component to add two methods to Prisma C - `$log` outputs a message. - `$totalQueries` returns the number of queries executed by the current client instance. + ```ts let total = 0; const prisma = new PrismaClient().$extends({ diff --git a/apps/docs/content/docs/orm/prisma-client/client-extensions/extension-examples.mdx b/apps/docs/content/docs/orm/prisma-client/client-extensions/extension-examples.mdx index 26209b1cf7..0902e5a751 100644 --- a/apps/docs/content/docs/orm/prisma-client/client-extensions/extension-examples.mdx +++ b/apps/docs/content/docs/orm/prisma-client/client-extensions/extension-examples.mdx @@ -10,9 +10,9 @@ metaDescription: Explore the Prisma Client extensions that have been built by Pr The following is a list of extensions we've built at Prisma: -| Extension | Description | -| :------------------------------------------------------------------------------------- | :----------------------------------------- | -| [`@prisma/extension-read-replicas`](https://github.com/prisma/extension-read-replicas) | Adds read replica support to Prisma Client | +| Extension | Description | +| :------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------- | +| [`@prisma/extension-read-replicas`](https://github.com/prisma/extension-read-replicas) | Adds read replica support to Prisma Client | ## Extensions made by Prisma's community diff --git a/apps/docs/content/docs/orm/prisma-client/client-extensions/model.mdx b/apps/docs/content/docs/orm/prisma-client/client-extensions/model.mdx index 474da05fa5..b34a381ef2 100644 --- a/apps/docs/content/docs/orm/prisma-client/client-extensions/model.mdx +++ b/apps/docs/content/docs/orm/prisma-client/client-extensions/model.mdx @@ -1,9 +1,9 @@ --- title: Add custom methods to your models -description: "Extend the functionality of Prisma Client, model component" +description: 'Extend the functionality of Prisma Client, model component' url: /orm/prisma-client/client-extensions/model -metaTitle: "Prisma Client extensions: model component" -metaDescription: "Extend the functionality of Prisma Client, model component" +metaTitle: 'Prisma Client extensions: model component' +metaDescription: 'Extend the functionality of Prisma Client, model component' --- You can use the `model` [Prisma Client extensions](/orm/prisma-client/client-extensions) component type to add custom methods to your models. diff --git a/apps/docs/content/docs/orm/prisma-client/client-extensions/query.mdx b/apps/docs/content/docs/orm/prisma-client/client-extensions/query.mdx index d2b23e5f65..83a70cee3f 100644 --- a/apps/docs/content/docs/orm/prisma-client/client-extensions/query.mdx +++ b/apps/docs/content/docs/orm/prisma-client/client-extensions/query.mdx @@ -1,9 +1,9 @@ --- title: Create custom Prisma Client queries -description: "Extend the functionality of Prisma Client, query component" +description: 'Extend the functionality of Prisma Client, query component' url: /orm/prisma-client/client-extensions/query -metaTitle: "Prisma Client extensions: query component" -metaDescription: "Extend the functionality of Prisma Client, query component" +metaTitle: 'Prisma Client extensions: query component' +metaDescription: 'Extend the functionality of Prisma Client, query component' --- You can use the `query` [Prisma Client extensions](/orm/prisma-client/client-extensions) component type to hook into the query life-cycle and modify an incoming query or its result. diff --git a/apps/docs/content/docs/orm/prisma-client/client-extensions/result.mdx b/apps/docs/content/docs/orm/prisma-client/client-extensions/result.mdx index b41bc4abfb..9a436244cd 100644 --- a/apps/docs/content/docs/orm/prisma-client/client-extensions/result.mdx +++ b/apps/docs/content/docs/orm/prisma-client/client-extensions/result.mdx @@ -1,9 +1,9 @@ --- title: Add custom fields and methods to query results -description: "Extend the functionality of Prisma Client, result component" +description: 'Extend the functionality of Prisma Client, result component' url: /orm/prisma-client/client-extensions/result -metaTitle: "Prisma Client extensions: result component" -metaDescription: "Extend the functionality of Prisma Client, result component" +metaTitle: 'Prisma Client extensions: result component' +metaDescription: 'Extend the functionality of Prisma Client, result component' --- You can use the `result` [Prisma Client extensions](/orm/prisma-client/client-extensions) component type to add custom fields and methods to query results. diff --git a/apps/docs/content/docs/orm/prisma-client/client-extensions/shared-extensions/index.mdx b/apps/docs/content/docs/orm/prisma-client/client-extensions/shared-extensions/index.mdx index 09be4d3994..390a65e0f2 100644 --- a/apps/docs/content/docs/orm/prisma-client/client-extensions/shared-extensions/index.mdx +++ b/apps/docs/content/docs/orm/prisma-client/client-extensions/shared-extensions/index.mdx @@ -2,8 +2,9 @@ title: Shared Prisma Client extensions description: Share extensions or import shared extensions into your Prisma project url: /orm/prisma-client/client-extensions/shared-extensions -metaTitle: "Shared Prisma Client extensions" -metaDescription: "Share extensions or import shared extensions into your Prisma project" +metaTitle: 'Shared Prisma Client extensions' +metaDescription: 'Share extensions or import shared extensions into your Prisma project' + --- You can share your [Prisma Client extensions](/orm/prisma-client/client-extensions) with other users, either as packages or as modules, and import extensions that other users create into your project. diff --git a/apps/docs/content/docs/orm/prisma-client/client-extensions/shared-extensions/permit-rbac.mdx b/apps/docs/content/docs/orm/prisma-client/client-extensions/shared-extensions/permit-rbac.mdx index 3e4cf8c83b..ee956c4042 100644 --- a/apps/docs/content/docs/orm/prisma-client/client-extensions/shared-extensions/permit-rbac.mdx +++ b/apps/docs/content/docs/orm/prisma-client/client-extensions/shared-extensions/permit-rbac.mdx @@ -1,8 +1,8 @@ --- title: Fine-Grained Authorization (Permit) -description: "Learn how to implement RBAC, ABAC, and ReBAC authorization in your Prisma applications" +description: 'Learn how to implement RBAC, ABAC, and ReBAC authorization in your Prisma applications' url: /orm/prisma-client/client-extensions/shared-extensions/permit-rbac -metaDescription: "Learn how to implement RBAC, ABAC, and ReBAC authorization in your Prisma applications" +metaDescription: 'Learn how to implement RBAC, ABAC, and ReBAC authorization in your Prisma applications' metaTitle: Fine-Grained Authorization (Permit) --- diff --git a/apps/docs/content/docs/orm/prisma-client/client-extensions/type-utilities.mdx b/apps/docs/content/docs/orm/prisma-client/client-extensions/type-utilities.mdx index 7037fb913a..65fa343c4b 100644 --- a/apps/docs/content/docs/orm/prisma-client/client-extensions/type-utilities.mdx +++ b/apps/docs/content/docs/orm/prisma-client/client-extensions/type-utilities.mdx @@ -1,9 +1,9 @@ --- title: Type utilities -description: "Advanced type safety: improve type safety in your custom model methods" +description: 'Advanced type safety: improve type safety in your custom model methods' url: /orm/prisma-client/client-extensions/type-utilities -metaTitle: "Prisma Client Extensions: Type utilities" -metaDescription: "Advanced type safety: improve type safety in your custom model methods" +metaTitle: 'Prisma Client Extensions: Type utilities' +metaDescription: 'Advanced type safety: improve type safety in your custom model methods' --- Several type utilities exist within Prisma Client that can assist in the creation of highly type-safe extensions. diff --git a/apps/docs/content/docs/orm/prisma-client/deployment/caveats-when-deploying-to-aws-platforms.mdx b/apps/docs/content/docs/orm/prisma-client/deployment/caveats-when-deploying-to-aws-platforms.mdx index f8805b0063..c0c798c10e 100644 --- a/apps/docs/content/docs/orm/prisma-client/deployment/caveats-when-deploying-to-aws-platforms.mdx +++ b/apps/docs/content/docs/orm/prisma-client/deployment/caveats-when-deploying-to-aws-platforms.mdx @@ -64,3 +64,4 @@ The `sslmode=no-verify` setting passes `rejectUnauthorized: false` to the SSL co ### Note While using `sslmode=no-verify` can be a quick fix, it bypasses SSL verification and might not meet security requirements for production environments. In such cases, ensure that a valid SSL certificate is properly configured. + diff --git a/apps/docs/content/docs/orm/prisma-client/deployment/deploy-migrations-from-a-local-environment.mdx b/apps/docs/content/docs/orm/prisma-client/deployment/deploy-migrations-from-a-local-environment.mdx index dab1903853..2b2c9bc08b 100644 --- a/apps/docs/content/docs/orm/prisma-client/deployment/deploy-migrations-from-a-local-environment.mdx +++ b/apps/docs/content/docs/orm/prisma-client/deployment/deploy-migrations-from-a-local-environment.mdx @@ -28,7 +28,7 @@ DATABASE_URL="postgresql://johndoe:randompassword@prod-db.example.com:5432/my_pr 3. Run `prisma migrate deploy` -**⛔ We strongly discourage this solution due to the following reasons** +**⛔ We strongly discourage this solution due to the following reasons** - You risk exposing your production database connection URL to version control. - You may accidentally use your production connection URL instead and in turn **override or delete your production database**. diff --git a/apps/docs/content/docs/orm/prisma-client/deployment/edge/deploy-to-cloudflare.mdx b/apps/docs/content/docs/orm/prisma-client/deployment/edge/deploy-to-cloudflare.mdx index ae3d36ebd7..d70d53abb3 100644 --- a/apps/docs/content/docs/orm/prisma-client/deployment/edge/deploy-to-cloudflare.mdx +++ b/apps/docs/content/docs/orm/prisma-client/deployment/edge/deploy-to-cloudflare.mdx @@ -41,6 +41,8 @@ This command: - Creates a `prisma` directory containing a `schema.prisma` file for your database models. - Creates a `.env` file with your `DATABASE_URL`. + + ### Using an edge-compatible driver When deploying a Cloudflare Worker that uses Prisma ORM, you need to use an [edge-compatible driver](/orm/prisma-client/deployment/edge/overview#edge-compatibility-of-database-drivers) and its respective [driver adapter](/orm/core-concepts/supported-databases/database-drivers#driver-adapters) for Prisma ORM. @@ -57,6 +59,7 @@ There's [also work being done](https://github.com/sidorares/node-mysql2/pull/228 If your application uses PostgreSQL, we recommend using [Prisma Postgres](/postgres). It is fully supported on edge runtimes and does not require a specialized edge-compatible driver. Review the [Prisma Postgres serverless driver limitations](/postgres/database/serverless-driver#limitations) to understand current constraints. + ### Setting your database connection URL as an environment variable First, ensure that your `datasource` block in your Prisma schema is configured correctly. Database connection URLs are configured in `prisma.config.ts`: diff --git a/apps/docs/content/docs/orm/prisma-client/deployment/edge/deploy-to-deno-deploy.mdx b/apps/docs/content/docs/orm/prisma-client/deployment/edge/deploy-to-deno-deploy.mdx index 937dcbd323..40780e616b 100644 --- a/apps/docs/content/docs/orm/prisma-client/deployment/edge/deploy-to-deno-deploy.mdx +++ b/apps/docs/content/docs/orm/prisma-client/deployment/edge/deploy-to-deno-deploy.mdx @@ -29,7 +29,6 @@ deno run -A npm:prisma@latest init --db Enter a name for your project and choose a database region. This command: - - Connects to the [Prisma Data Platform](https://console.prisma.io) (opens browser for authentication) - Creates a `prisma/schema.prisma` file for your database models - Creates a `.env` file with your `DATABASE_URL` @@ -106,7 +105,6 @@ deno task db:push ``` This command: - 1. Creates the `Task` table in your Prisma Postgres database 2. Generates the Prisma Client with full type safety @@ -218,14 +216,14 @@ Deno.serve({ port: 8000 }, handler); This creates a full CRUD API with the following endpoints: -| Method | Endpoint | Description | -| ------ | ------------ | ------------------- | -| GET | `/` | API info | -| GET | `/tasks` | List all tasks | -| POST | `/tasks` | Create a new task | -| GET | `/tasks/:id` | Get a specific task | -| PATCH | `/tasks/:id` | Update a task | -| DELETE | `/tasks/:id` | Delete a task | +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/` | API info | +| GET | `/tasks` | List all tasks | +| POST | `/tasks` | Create a new task | +| GET | `/tasks/:id` | Get a specific task | +| PATCH | `/tasks/:id` | Update a task | +| DELETE | `/tasks/:id` | Delete a task | ## 6. Test your application locally diff --git a/apps/docs/content/docs/orm/prisma-client/deployment/edge/overview.mdx b/apps/docs/content/docs/orm/prisma-client/deployment/edge/overview.mdx index aa7fff24db..3fc7f34a5c 100644 --- a/apps/docs/content/docs/orm/prisma-client/deployment/edge/overview.mdx +++ b/apps/docs/content/docs/orm/prisma-client/deployment/edge/overview.mdx @@ -2,7 +2,7 @@ title: Deploying edge functions with Prisma ORM description: Learn how to deploy your Prisma-backed apps to edge functions like Cloudflare Workers or Vercel Edge Functions url: /orm/prisma-client/deployment/edge/overview -metaTitle: "Overview: Deploy Prisma ORM at the Edge" +metaTitle: 'Overview: Deploy Prisma ORM at the Edge' metaDescription: Learn how to deploy your Prisma-backed apps to edge functions like Cloudflare Workers or Vercel Edge Functions --- @@ -29,6 +29,7 @@ Here is a brief overview of all the edge function providers that are currently s Deploying edge functions that use Prisma ORM on Cloudflare and Vercel is currently in [Preview](/orm/more/releases#preview). + ## Edge-compatibility of database drivers ### Why are there limitations around database drivers in edge functions? @@ -39,7 +40,7 @@ In particular, the constraint of not being able to freely open TCP connections m :::note -We recommend using [Prisma Postgres](/postgres). It is fully supported on edge runtimes and does not require a specialized edge-compatible driver. +We recommend using [Prisma Postgres](/postgres). It is fully supported on edge runtimes and does not require a specialized edge-compatible driver. ::: diff --git a/apps/docs/content/docs/orm/prisma-client/deployment/serverless/deploy-to-aws-lambda.mdx b/apps/docs/content/docs/orm/prisma-client/deployment/serverless/deploy-to-aws-lambda.mdx index e9b40b0eff..0108dbaf85 100644 --- a/apps/docs/content/docs/orm/prisma-client/deployment/serverless/deploy-to-aws-lambda.mdx +++ b/apps/docs/content/docs/orm/prisma-client/deployment/serverless/deploy-to-aws-lambda.mdx @@ -1,9 +1,9 @@ --- title: Deploy to AWS Lambda -description: "Learn how to deploy your Prisma ORM-backed applications to AWS Lambda with AWS SAM, Serverless Framework, or SST" +description: 'Learn how to deploy your Prisma ORM-backed applications to AWS Lambda with AWS SAM, Serverless Framework, or SST' url: /orm/prisma-client/deployment/serverless/deploy-to-aws-lambda metaTitle: Deploy your application using Prisma ORM to AWS Lambda -metaDescription: "Learn how to deploy your Prisma ORM-backed applications to AWS Lambda with AWS SAM, Serverless Framework, or SST" +metaDescription: 'Learn how to deploy your Prisma ORM-backed applications to AWS Lambda with AWS SAM, Serverless Framework, or SST' --- :::info[Quick summary] @@ -81,6 +81,7 @@ Packaging deployment-example-sls for stage dev (us-east-1) . ``` + ## Deploying with SST ### Working with environment variables @@ -97,7 +98,9 @@ const globalForPrisma = global as unknown as { prisma: PrismaClient }; const adapter = new PrismaPg({ connectionString }); -export const prisma = globalForPrisma.prisma || new PrismaClient({ adapter }); +export const prisma = + globalForPrisma.prisma || + new PrismaClient({ adapter }); if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma; diff --git a/apps/docs/content/docs/orm/prisma-client/observability-and-logging/sql-comments.mdx b/apps/docs/content/docs/orm/prisma-client/observability-and-logging/sql-comments.mdx index 9757322dd3..2e704bab16 100644 --- a/apps/docs/content/docs/orm/prisma-client/observability-and-logging/sql-comments.mdx +++ b/apps/docs/content/docs/orm/prisma-client/observability-and-logging/sql-comments.mdx @@ -1,9 +1,9 @@ --- title: SQL comments -description: "Add metadata to your SQL queries as comments for improved observability, debugging, and tracing" +description: 'Add metadata to your SQL queries as comments for improved observability, debugging, and tracing' url: /orm/prisma-client/observability-and-logging/sql-comments metaTitle: SQL comments -metaDescription: "Add metadata to your SQL queries as comments for improved observability, debugging, and tracing." +metaDescription: 'Add metadata to your SQL queries as comments for improved observability, debugging, and tracing.' --- SQL comments allow you to append metadata to your database queries, making it easier to correlate queries with application context. Prisma ORM supports the [sqlcommenter format](https://google.github.io/sqlcommenter/) developed by Google, which is widely supported by database monitoring tools. diff --git a/apps/docs/content/docs/orm/prisma-client/queries/advanced/query-optimization-performance.mdx b/apps/docs/content/docs/orm/prisma-client/queries/advanced/query-optimization-performance.mdx index 14a5b727cc..7eed33f1eb 100644 --- a/apps/docs/content/docs/orm/prisma-client/queries/advanced/query-optimization-performance.mdx +++ b/apps/docs/content/docs/orm/prisma-client/queries/advanced/query-optimization-performance.mdx @@ -46,7 +46,6 @@ see Prisma ORM queries in Query Insights. Docs: https://www.prisma.io/docs/query ## Debugging performance issues Common causes of slow queries: - - Over-fetching data - Missing indexes - Not caching repeated queries @@ -115,7 +114,9 @@ Prisma's dataloader automatically batches `findUnique()` queries in the same tic ```ts // Instead of findMany per user, use: -return context.prisma.user.findUnique({ where: { id: parent.id } }).posts(); +return context.prisma.user + .findUnique({ where: { id: parent.id } }) + .posts(); ``` ### Using JOINs with `relationLoadStrategy` @@ -347,13 +348,13 @@ const usersWithPosts = await prisma.user.findMany({ // GOOD: 2 queries with in filter const users = await prisma.user.findMany({}); const posts = await prisma.post.findMany({ - where: { authorId: { in: users.map((u) => u.id) } }, + where: { authorId: { in: users.map(u => u.id) } }, }); // BEST: 1 query with join const posts = await prisma.post.findMany({ relationLoadStrategy: "join", - where: { authorId: { in: users.map((u) => u.id) } }, + where: { authorId: { in: users.map(u => u.id) } }, }); ``` diff --git a/apps/docs/content/docs/orm/prisma-client/queries/aggregation-grouping-summarizing.mdx b/apps/docs/content/docs/orm/prisma-client/queries/aggregation-grouping-summarizing.mdx index 24609549c2..b8b2ff0cc1 100644 --- a/apps/docs/content/docs/orm/prisma-client/queries/aggregation-grouping-summarizing.mdx +++ b/apps/docs/content/docs/orm/prisma-client/queries/aggregation-grouping-summarizing.mdx @@ -1,9 +1,9 @@ --- -title: "Aggregation, grouping, and summarizing" -description: "Use Prisma Client to aggregate, group by, count, and select distinct." +title: 'Aggregation, grouping, and summarizing' +description: 'Use Prisma Client to aggregate, group by, count, and select distinct.' url: /orm/prisma-client/queries/aggregation-grouping-summarizing -metaTitle: "Aggregation, grouping, and summarizing (Concepts)" -metaDescription: "Use Prisma Client to aggregate, group by, count, and select distinct." +metaTitle: 'Aggregation, grouping, and summarizing (Concepts)' +metaDescription: 'Use Prisma Client to aggregate, group by, count, and select distinct.' --- Prisma Client allows you to count records, aggregate number fields, and select distinct field values. @@ -17,7 +17,7 @@ const aggregations = await prisma.user.aggregate({ _avg: { age: true }, }); -console.log("Average age:" + aggregations._avg.age); +console.log('Average age:' + aggregations._avg.age); ``` You can combine aggregation with filtering and ordering. For example, the following query returns the average age of users: @@ -31,14 +31,14 @@ const aggregations = await prisma.user.aggregate({ _avg: { age: true }, where: { email: { - contains: "prisma.io", + contains: 'prisma.io', }, }, - orderBy: { age: "asc" }, + orderBy: { age: 'asc' }, take: 10, }); -console.log("Average age:" + aggregations._avg.age); +console.log('Average age:' + aggregations._avg.age); ``` ### Aggregate values are nullable @@ -76,7 +76,7 @@ The following example groups all users by the `country` field and returns the to ```ts const groupUsers = await prisma.user.groupBy({ - by: ["country"], + by: ['country'], _sum: { profileViews: true }, }); ``` @@ -92,7 +92,7 @@ If you have a single element in the `by` option, you can use the following short ```ts const groupUsers = await prisma.user.groupBy({ - by: "country", + by: 'country', }); ``` @@ -106,12 +106,12 @@ Use `where` to filter all records **before grouping**. The following example gro ```ts const groupUsers = await prisma.user.groupBy({ - by: ["country"], + by: ['country'], where: { // [!code highlight] email: { // [!code highlight] - contains: "prisma.io", // [!code highlight] + contains: 'prisma.io', // [!code highlight] }, // [!code highlight] }, // [!code highlight] _sum: { @@ -126,13 +126,13 @@ Use `having` to filter **entire groups** by an aggregate value such as the sum o ```ts const groupUsers = await prisma.user.groupBy({ - by: ["country"], + by: ['country'], where: { email: { - contains: "prisma.io", + contains: 'prisma.io', }, }, - _sum: { profileViews: true }, + _sum: { profileViews: true, }, having: { // [!code highlight] profileViews: { @@ -154,11 +154,11 @@ For example, the following query groups all users that are _not_ from Sweden or ```ts const fd = await prisma.user.groupBy({ - by: ["country"], + by: ['country'], where: { country: { // [!code highlight] - notIn: ["Sweden", "Ghana"], // [!code highlight] + notIn: ['Sweden', 'Ghana'], // [!code highlight] }, // [!code highlight] }, _sum: { @@ -178,11 +178,11 @@ The following query technically achieves the same result, but excludes users fro ```ts const groupUsers = await prisma.user.groupBy({ - by: ["country"], + by: ['country'], where: { country: { // [!code highlight] - not: "Sweden", // [!code highlight] + not: 'Sweden', // [!code highlight] }, // [!code highlight] }, _sum: { @@ -191,7 +191,7 @@ const groupUsers = await prisma.user.groupBy({ having: { country: { // [!code highlight] - not: "Ghana", // [!code highlight] + not: 'Ghana', // [!code highlight] }, // [!code highlight] profileViews: { _min: { @@ -218,13 +218,13 @@ You can **order by aggregate group**. The following example sorts each `city` gr ```ts const groupBy = await prisma.user.groupBy({ - by: ["city"], + by: ['city'], _count: { city: true, }, orderBy: { _count: { - city: "desc", + city: 'desc', }, }, }); @@ -244,12 +244,12 @@ The following query orders groups by country, skips the first two groups, and re ```ts const groupBy = await prisma.user.groupBy({ - by: ["country"], + by: ['country'], _sum: { profileViews: true, }, orderBy: { - country: "desc", + country: 'desc', }, skip: 2, take: 2, @@ -391,7 +391,7 @@ await prisma.user.findMany({ select: { _count: { select: { - posts: { where: { title: "Hello!" } }, + posts: { where: { title: 'Hello!' } }, }, }, }, @@ -408,7 +408,7 @@ await prisma.user.findMany({ _count: { select: { posts: { - where: { comments: { some: { author: { is: { name: "Alice" } } } } }, + where: { comments: { some: { author: { is: { name: 'Alice' } } } } }, }, }, }, @@ -469,7 +469,7 @@ The following example returns all fields for all `User` records with distinct `n ```ts const result = await prisma.user.findMany({ where: {}, - distinct: ["name"], + distinct: ['name'], }); ``` @@ -477,7 +477,7 @@ The following example returns distinct `role` field values (for example, `ADMIN` ```ts const distinctRoles = await prisma.user.findMany({ - distinct: ["role"], + distinct: ['role'], select: { role: true, }, @@ -537,9 +537,9 @@ model Play { ```ts const distinctScores = await prisma.play.findMany({ - distinct: ["playerId", "gameId"], + distinct: ['playerId', 'gameId'], orderBy: { - score: "desc", + score: 'desc', }, select: { score: true, diff --git a/apps/docs/content/docs/orm/prisma-client/queries/relation-queries.mdx b/apps/docs/content/docs/orm/prisma-client/queries/relation-queries.mdx index 3d7b3753f1..0f7d82a9f2 100644 --- a/apps/docs/content/docs/orm/prisma-client/queries/relation-queries.mdx +++ b/apps/docs/content/docs/orm/prisma-client/queries/relation-queries.mdx @@ -1,9 +1,9 @@ --- title: Relation queries -description: "Prisma Client provides convenient queries for working with relations, such as a fluent API, nested writes (transactions), nested reads and relation filters" +description: 'Prisma Client provides convenient queries for working with relations, such as a fluent API, nested writes (transactions), nested reads and relation filters' url: /orm/prisma-client/queries/relation-queries metaTitle: Relation queries (Concepts) -metaDescription: "Prisma Client provides convenient queries for working with relations, such as a fluent API, nested writes (transactions), nested reads and relation filters." +metaDescription: 'Prisma Client provides convenient queries for working with relations, such as a fluent API, nested writes (transactions), nested reads and relation filters.' --- A key feature of Prisma Client is the ability to query [relations](/orm/prisma-schema/data-model/relations) between two or more models. Relation queries include: @@ -90,30 +90,30 @@ const user = await prisma.user.findFirst({ ```json { - "id": 19, - "name": null, - "email": "emma@prisma.io", - "profileViews": 0, - "role": "USER", - "coinflips": [], - "posts": [ + id: 19, + name: null, + email: 'emma@prisma.io', + profileViews: 0, + role: 'USER', + coinflips: [], + posts: [ { - "id": 20, - "title": "My first post", - "published": true, - "authorId": 19, - "comments": null, - "views": 0, - "likes": 0 + id: 20, + title: 'My first post', + published: true, + authorId: 19, + comments: null, + views: 0, + likes: 0 }, { - "id": 21, - "title": "How to make cookies", - "published": true, - "authorId": 19, - "comments": null, - "views": 0, - "likes": 0 + id: 21, + title: 'How to make cookies', + published: true, + authorId: 19, + comments: null, + views: 0, + likes: 0 } ] } @@ -133,21 +133,21 @@ const post = await prisma.post.findFirst({ ```json { - "id": 17, - "title": "How to make cookies", - "published": true, - "authorId": 16, - "comments": null, - "views": 0, - "likes": 0, - "author": { - "id": 16, - "name": null, - "email": "orla@prisma.io", - "profileViews": 0, - "role": "USER", - "coinflips": [] - } + id: 17, + title: 'How to make cookies', + published: true, + authorId: 16, + comments: null, + views: 0, + likes: 0, + author: { + id: 16, + name: null, + email: 'orla@prisma.io', + profileViews: 0, + role: 'USER', + coinflips: [], + }, } ``` @@ -169,42 +169,42 @@ const user = await prisma.user.findFirst({ ```json { - "id": 40, - "name": "Yvette", - "email": "yvette@prisma.io", - "profileViews": 0, - "role": "USER", - "coinflips": [], - "testing": [], - "city": null, - "country": "Sweden", - "posts": [ - { - "id": 66, - "title": "How to make an omelette", - "published": true, - "authorId": 40, - "comments": null, - "views": 0, - "likes": 0, - "categories": [ + "id": 40, + "name": "Yvette", + "email": "yvette@prisma.io", + "profileViews": 0, + "role": "USER", + "coinflips": [], + "testing": [], + "city": null, + "country": "Sweden", + "posts": [ + { + "id": 66, + "title": "How to make an omelette", + "published": true, + "authorId": 40, + "comments": null, + "views": 0, + "likes": 0, + "categories": [ + { + "id": 3, + "name": "Easy cooking" + } + ] + }, { - "id": 3, - "name": "Easy cooking" + "id": 67, + "title": "How to eat an omelette", + "published": true, + "authorId": 40, + "comments": null, + "views": 0, + "likes": 0, + "categories": [] } - ] - }, - { - "id": 67, - "title": "How to eat an omelette", - "published": true, - "authorId": 40, - "comments": null, - "views": 0, - "likes": 0, - "categories": [] - } - ] + ] } ``` @@ -227,8 +227,8 @@ const user = await prisma.user.findFirst({ ```json { - "name": "Elsa", - "posts": [{ "title": "My first post" }, { "title": "How to make cookies" }] + name: "Elsa", + posts: [ { title: 'My first post' }, { title: 'How to make cookies' } ] } ``` @@ -254,7 +254,10 @@ const user = await prisma.user.findFirst({ "profileViews": 0, "role": "USER", "coinflips": [], - "posts": [{ "title": "How to grow salad" }, { "title": "How to ride a horse" }] + "posts": [ + { "title": "How to grow salad" }, + { "title": "How to ride a horse" } + ] } ``` @@ -412,30 +415,30 @@ const result = await prisma.user.create({ ```json { - "id": 29, - "name": "Elsa", - "email": "elsa@prisma.io", - "profileViews": 0, - "role": "USER", - "coinflips": [], - "posts": [ + id: 29, + name: 'Elsa', + email: 'elsa@prisma.io', + profileViews: 0, + role: 'USER', + coinflips: [], + posts: [ { - "id": 22, - "title": "How to make an omelette", - "published": true, - "authorId": 29, - "comments": null, - "views": 0, - "likes": 0 + id: 22, + title: 'How to make an omelette', + published: true, + authorId: 29, + comments: null, + views: 0, + likes: 0 }, { - "id": 23, - "title": "How to eat an omelette", - "published": true, - "authorId": 29, - "comments": null, - "views": 0, - "likes": 0 + id: 23, + title: 'How to eat an omelette', + published: true, + authorId: 29, + comments: null, + views: 0, + likes: 0 } ] } @@ -504,42 +507,42 @@ const result = await prisma.user.create({ ```json { - "id": 40, - "name": "Yvette", - "email": "yvette@prisma.io", - "profileViews": 0, - "role": "USER", - "coinflips": [], - "testing": [], - "city": null, - "country": "Sweden", - "posts": [ - { - "id": 66, - "title": "How to make an omelette", - "published": true, - "authorId": 40, - "comments": null, - "views": 0, - "likes": 0, - "categories": [ + "id": 40, + "name": "Yvette", + "email": "yvette@prisma.io", + "profileViews": 0, + "role": "USER", + "coinflips": [], + "testing": [], + "city": null, + "country": "Sweden", + "posts": [ { - "id": 3, - "name": "Easy cooking" + "id": 66, + "title": "How to make an omelette", + "published": true, + "authorId": 40, + "comments": null, + "views": 0, + "likes": 0, + "categories": [ + { + "id": 3, + "name": "Easy cooking" + } + ] + }, + { + "id": 67, + "title": "How to eat an omelette", + "published": true, + "authorId": 40, + "comments": null, + "views": 0, + "likes": 0, + "categories": [] } - ] - }, - { - "id": 67, - "title": "How to eat an omelette", - "published": true, - "authorId": 40, - "comments": null, - "views": 0, - "likes": 0, - "categories": [] - } - ] + ] } ``` @@ -576,35 +579,35 @@ const result = await prisma.user.create({ ```json { - "id": 43, - "name": null, - "email": "saanvi@prisma.io", - "profileViews": 0, - "role": "USER", - "coinflips": [], - "testing": [], - "city": null, - "country": "India", - "posts": [ - { - "id": 70, - "title": "My first post", - "published": true, - "authorId": 43, - "comments": null, - "views": 0, - "likes": 0 - }, - { - "id": 71, - "title": "My second post", - "published": true, - "authorId": 43, - "comments": null, - "views": 0, - "likes": 0 - } - ] + "id": 43, + "name": null, + "email": "saanvi@prisma.io", + "profileViews": 0, + "role": "USER", + "coinflips": [], + "testing": [], + "city": null, + "country": "India", + "posts": [ + { + "id": 70, + "title": "My first post", + "published": true, + "authorId": 43, + "comments": null, + "views": 0, + "likes": 0 + }, + { + "id": 71, + "title": "My second post", + "published": true, + "authorId": 43, + "comments": null, + "views": 0, + "likes": 0 + } + ] } ``` @@ -688,21 +691,21 @@ const result = await prisma.user.create({ ```json { - "id": 27, - "name": null, - "email": "vlad@prisma.io", - "profileViews": 0, - "role": "USER", - "coinflips": [], - "posts": [ + id: 27, + name: null, + email: 'vlad@prisma.io', + profileViews: 0, + role: 'USER', + coinflips: [], + posts: [ { - "id": 10, - "title": "An existing post", - "published": true, - "authorId": 27, - "comments": {}, - "views": 0, - "likes": 0 + id: 10, + title: 'An existing post', + published: true, + authorId: 27, + comments: {}, + views: 0, + likes: 0 } ] } @@ -771,19 +774,19 @@ const result = await prisma.post.create({ ```json { - "id": 26, - "title": "How to make croissants", - "published": true, - "authorId": 43, - "views": 0, - "likes": 0, - "author": { - "id": 43, - "name": "Viola", - "email": "viola@prisma.io", - "profileViews": 0, - "role": "USER", - "coinflips": [] + id: 26, + title: 'How to make croissants', + published: true, + authorId: 43, + views: 0, + likes: 0, + author: { + id: 43, + name: 'Viola', + email: 'viola@prisma.io', + profileViews: 0, + role: 'USER', + coinflips: [] } } ``` @@ -811,13 +814,13 @@ const result = await prisma.user.update({ ```json { - "id": 16, - "name": null, - "email": "orla@prisma.io", - "profileViews": 0, - "role": "USER", - "coinflips": [], - "posts": [] + id: 16, + name: null, + email: 'orla@prisma.io', + profileViews: 0, + role: 'USER', + coinflips: [], + posts: [] } ``` @@ -842,14 +845,14 @@ const result = await prisma.post.update({ ```json { - "id": 23, - "title": "How to eat an omelette", - "published": true, - "authorId": null, - "comments": null, - "views": 0, - "likes": 0, - "author": null + id: 23, + title: 'How to eat an omelette', + published: true, + authorId: null, + comments: null, + views: 0, + likes: 0, + author: null } ``` @@ -876,13 +879,13 @@ const result = await prisma.user.update({ ```json { - "id": 16, - "name": null, - "email": "orla@prisma.io", - "profileViews": 0, - "role": "USER", - "coinflips": [], - "posts": [] + id: 16, + name: null, + email: 'orla@prisma.io', + profileViews: 0, + role: 'USER', + coinflips: [], + posts: [] } ``` @@ -1223,6 +1226,7 @@ const postsByUser = await prisma.post.findMany({ The main difference between the queries is that the fluent API call is translated into two separate database queries while the other one only generates a single query (see this [GitHub issue](https://github.com/prisma/prisma/issues/1984)) + This request returns all categories by a specific post: ```ts diff --git a/apps/docs/content/docs/orm/prisma-client/setup-and-configuration/databases-connections/connection-management.mdx b/apps/docs/content/docs/orm/prisma-client/setup-and-configuration/databases-connections/connection-management.mdx index d2475e0a42..8ace30491d 100644 --- a/apps/docs/content/docs/orm/prisma-client/setup-and-configuration/databases-connections/connection-management.mdx +++ b/apps/docs/content/docs/orm/prisma-client/setup-and-configuration/databases-connections/connection-management.mdx @@ -84,7 +84,7 @@ async function main() { main() .then(async () => { - await prisma.$disconnect(); //[!code highlight] + await prisma.$disconnect(); //[!code highlight] }) .catch(async (e) => { console.error(e); diff --git a/apps/docs/content/docs/orm/prisma-client/setup-and-configuration/databases-connections/index.mdx b/apps/docs/content/docs/orm/prisma-client/setup-and-configuration/databases-connections/index.mdx index 8319e125b5..3ae42d7cc3 100644 --- a/apps/docs/content/docs/orm/prisma-client/setup-and-configuration/databases-connections/index.mdx +++ b/apps/docs/content/docs/orm/prisma-client/setup-and-configuration/databases-connections/index.mdx @@ -95,8 +95,8 @@ main(); In local tests with Postgres, MySQL, and SQLite, each Prisma CLI command typically uses a single connection. The table below shows the ranges observed in these tests. Your environment _may_ produce slightly different results. -| Command | Connections | Description | -| ---------------------------------------------------------------------- | ----------- | ------------------------------------------------ | +| Command | Connections | Description | +| ------------------------------------------------------------------------------ | ----------- | ------------------------------------------------ | | [`migrate status`](/orm/reference/prisma-cli-reference#migrate-status) | 1 | Checks the status of migrations | | [`migrate dev`](/orm/reference/prisma-cli-reference#migrate-dev) | 1–4 | Applies pending migrations in development | | [`migrate diff`](/orm/reference/prisma-cli-reference#migrate-diff) | 1–2 | Compares database schema with migration history | diff --git a/apps/docs/content/docs/orm/prisma-client/setup-and-configuration/databases-connections/pgbouncer.mdx b/apps/docs/content/docs/orm/prisma-client/setup-and-configuration/databases-connections/pgbouncer.mdx index 75948ab286..b55f87ff6c 100644 --- a/apps/docs/content/docs/orm/prisma-client/setup-and-configuration/databases-connections/pgbouncer.mdx +++ b/apps/docs/content/docs/orm/prisma-client/setup-and-configuration/databases-connections/pgbouncer.mdx @@ -1,9 +1,9 @@ --- title: Configure Prisma Client with PgBouncer -description: "Configure Prisma Client with PgBouncer and other poolers: when to use pgbouncer=true, required transaction mode, prepared statements, and Prisma Migrate workarounds" +description: 'Configure Prisma Client with PgBouncer and other poolers: when to use pgbouncer=true, required transaction mode, prepared statements, and Prisma Migrate workarounds' url: /orm/prisma-client/setup-and-configuration/databases-connections/pgbouncer metaTitle: Configure Prisma Client with PgBouncer -metaDescription: "Configure Prisma Client with PgBouncer and other poolers: when to use pgbouncer=true, required transaction mode, prepared statements, and Prisma Migrate workarounds." +metaDescription: 'Configure Prisma Client with PgBouncer and other poolers: when to use pgbouncer=true, required transaction mode, prepared statements, and Prisma Migrate workarounds.' --- An external connection pooler like PgBouncer holds a connection pool to the database, and proxies incoming client connections by sitting between Prisma Client and the database. This reduces the number of processes a database has to handle at any given time. diff --git a/apps/docs/content/docs/orm/prisma-client/setup-and-configuration/introduction.mdx b/apps/docs/content/docs/orm/prisma-client/setup-and-configuration/introduction.mdx index 9b6cfc3c38..dde5fcc9de 100644 --- a/apps/docs/content/docs/orm/prisma-client/setup-and-configuration/introduction.mdx +++ b/apps/docs/content/docs/orm/prisma-client/setup-and-configuration/introduction.mdx @@ -15,13 +15,13 @@ Prisma Client is an auto-generated and type-safe query builder that's _tailored_ In order to set up Prisma Client, you need a Prisma Config and a [Prisma schema file](/orm/prisma-schema/overview): ```ts title="prisma.config.ts" tab="Prisma Config" -import "dotenv/config"; -import { defineConfig, env } from "prisma/config"; +import 'dotenv/config'; +import { defineConfig, env } from 'prisma/config'; export default defineConfig({ - schema: "./prisma/schema.prisma", + schema: './prisma/schema.prisma', datasource: { - url: env("DATABASE_URL"), + url: env('DATABASE_URL'), }, }); ``` @@ -146,6 +146,7 @@ Error in connector: Error querying the database: db error: FATAL: sorry, too man at PrismaClientFetcher.request ``` + ## Use Prisma Client to send queries to your database Once you have instantiated `PrismaClient`, you can start sending queries in your code: @@ -166,6 +167,6 @@ const users = await prisma.user.findMany(); Whenever you make changes to your database that are reflected in the Prisma schema, you need to manually re-generate Prisma Client to update the generated code in your output directory: -```npm +```npm npx prisma generate ``` diff --git a/apps/docs/content/docs/orm/prisma-client/special-fields-and-types/working-with-composite-ids-and-constraints.mdx b/apps/docs/content/docs/orm/prisma-client/special-fields-and-types/working-with-composite-ids-and-constraints.mdx index 83379fa72b..2225531ae1 100644 --- a/apps/docs/content/docs/orm/prisma-client/special-fields-and-types/working-with-composite-ids-and-constraints.mdx +++ b/apps/docs/content/docs/orm/prisma-client/special-fields-and-types/working-with-composite-ids-and-constraints.mdx @@ -1,9 +1,9 @@ --- title: Working with compound IDs and unique constraints -description: "How to read, write, and filter by compound IDs and unique constraints" +description: 'How to read, write, and filter by compound IDs and unique constraints' url: /orm/prisma-client/special-fields-and-types/working-with-composite-ids-and-constraints metaTitle: Working with compound IDs and unique constraints (Concepts) -metaDescription: "How to read, write, and filter by compound IDs and unique constraints." +metaDescription: 'How to read, write, and filter by compound IDs and unique constraints.' --- Composite IDs and compound unique constraints can be defined in your Prisma schema using the [`@@id`](/orm/reference/prisma-schema-reference) and [`@@unique`](/orm/reference/prisma-schema-reference) attributes. diff --git a/apps/docs/content/docs/orm/prisma-client/special-fields-and-types/working-with-geometry-fields.mdx b/apps/docs/content/docs/orm/prisma-client/special-fields-and-types/working-with-geometry-fields.mdx index 004cb752ab..75e7134993 100644 --- a/apps/docs/content/docs/orm/prisma-client/special-fields-and-types/working-with-geometry-fields.mdx +++ b/apps/docs/content/docs/orm/prisma-client/special-fields-and-types/working-with-geometry-fields.mdx @@ -549,7 +549,9 @@ const availableZones = await prisma.deliveryZone.findMany({ const canDeliver = availableZones.length > 0; if (canDeliver) { - console.log(`Delivery available in zones: ${availableZones.map((z) => z.name).join(", ")}`); + console.log( + `Delivery available in zones: ${availableZones.map((z) => z.name).join(", ")}` + ); } else { console.log("Sorry, we don't deliver to this address yet"); } @@ -762,14 +764,15 @@ If you're using **PostgreSQL 16** with datasets containing thousands of records, **Workaround:** Use the `withSpatialOptimization` helper from `@prisma/adapter-pg`: ```typescript -import { PrismaClient } from "@prisma/client"; -import { withSpatialOptimization } from "@prisma/adapter-pg"; +import { PrismaClient } from '@prisma/client' +import { withSpatialOptimization } from '@prisma/adapter-pg' -const userLocation = [13.4, 52.5] as [number, number]; +const userLocation = [13.4, 52.5] as [number, number] // The helper automatically detects PostgreSQL 16 and applies optimization only when needed -const nearby = await withSpatialOptimization(prisma, (client) => - client.location.findMany({ +const nearby = await withSpatialOptimization( + prisma, + (client) => client.location.findMany({ where: { position: { near: { @@ -784,26 +787,26 @@ const nearby = await withSpatialOptimization(prisma, (client) => }, }, take: 20, - }), -); + }) +) // Works with all spatial filters: -const zonesContaining = await withSpatialOptimization(prisma, (client) => - client.deliveryZone.findMany({ +const zonesContaining = await withSpatialOptimization( + prisma, + (client) => client.deliveryZone.findMany({ where: { boundary: { intersects: { - type: "Point", + type: 'Point', coordinates: userLocation, }, }, }, - }), -); + }) +) ``` The helper: - - **Automatically detects** PostgreSQL version (cached for performance) - **Only applies optimization** on PostgreSQL 16 - **Skips optimization** on PostgreSQL 17+ (where the bug is fixed) diff --git a/apps/docs/content/docs/orm/prisma-client/special-fields-and-types/working-with-json-fields.mdx b/apps/docs/content/docs/orm/prisma-client/special-fields-and-types/working-with-json-fields.mdx index 7ffa67fa68..06c752bf63 100644 --- a/apps/docs/content/docs/orm/prisma-client/special-fields-and-types/working-with-json-fields.mdx +++ b/apps/docs/content/docs/orm/prisma-client/special-fields-and-types/working-with-json-fields.mdx @@ -1,9 +1,9 @@ --- title: Working with Json fields -description: "How to read, write, and filter by Json fields" +description: 'How to read, write, and filter by Json fields' url: /orm/prisma-client/special-fields-and-types/working-with-json-fields metaTitle: Working with Json fields (Concepts) -metaDescription: "How to read, write, and filter by Json fields." +metaDescription: 'How to read, write, and filter by Json fields.' --- Use the [`Json`](/orm/reference/prisma-schema-reference#json) Prisma ORM field type to read, write, and perform basic filtering on JSON types in the underlying database. In the following example, the `User` model has an optional `Json` field named `extendedPetsData`: diff --git a/apps/docs/content/docs/orm/prisma-client/special-fields-and-types/working-with-scalar-lists-arrays.mdx b/apps/docs/content/docs/orm/prisma-client/special-fields-and-types/working-with-scalar-lists-arrays.mdx index 6629bf1f35..eeb3d30d04 100644 --- a/apps/docs/content/docs/orm/prisma-client/special-fields-and-types/working-with-scalar-lists-arrays.mdx +++ b/apps/docs/content/docs/orm/prisma-client/special-fields-and-types/working-with-scalar-lists-arrays.mdx @@ -1,9 +1,9 @@ --- title: Working with scalar lists -description: "How to read, write, and filter by scalar lists / arrays" +description: 'How to read, write, and filter by scalar lists / arrays' url: /orm/prisma-client/special-fields-and-types/working-with-scalar-lists-arrays metaTitle: Working with scalar lists/arrays (Concepts) -metaDescription: "How to read, write, and filter by scalar lists / arrays." +metaDescription: 'How to read, write, and filter by scalar lists / arrays.' --- [Scalar lists](/orm/reference/prisma-schema-reference#-modifier) are represented by the `[]` modifier and are only available if the underlying database supports scalar lists. The following example has one scalar `String` list named `pets`: diff --git a/apps/docs/content/docs/orm/prisma-client/type-safety/index.mdx b/apps/docs/content/docs/orm/prisma-client/type-safety/index.mdx index f47a592796..30af15e6ac 100644 --- a/apps/docs/content/docs/orm/prisma-client/type-safety/index.mdx +++ b/apps/docs/content/docs/orm/prisma-client/type-safety/index.mdx @@ -1,9 +1,10 @@ --- title: Type safety Overview -description: "Prisma Client provides full type safety for queries, even for partial queries or included relations. This page explains how to leverage the generated types and utilities" +description: 'Prisma Client provides full type safety for queries, even for partial queries or included relations. This page explains how to leverage the generated types and utilities' url: /orm/prisma-client/type-safety -metaTitle: "Type safety" -metaDescription: "Prisma Client provides full type safety for queries, even for partial queries or included relations. This page explains how to leverage the generated types and utilities." +metaTitle: 'Type safety' +metaDescription: 'Prisma Client provides full type safety for queries, even for partial queries or included relations. This page explains how to leverage the generated types and utilities.' + --- The generated code for Prisma Client contains several helpful types and utilities that you can use to make your application more type-safe. This page describes patterns for leveraging them. diff --git a/apps/docs/content/docs/orm/prisma-client/using-raw-sql/index.mdx b/apps/docs/content/docs/orm/prisma-client/using-raw-sql/index.mdx index 8c80907632..7c276c51f1 100644 --- a/apps/docs/content/docs/orm/prisma-client/using-raw-sql/index.mdx +++ b/apps/docs/content/docs/orm/prisma-client/using-raw-sql/index.mdx @@ -14,6 +14,7 @@ In most cases, [TypedSQL](#writing-type-safe-queries-with-prisma-client-and-type ## Writing type-safe queries with Prisma Client and TypedSQL + ### What is TypedSQL? TypedSQL is a new feature of Prisma ORM that allows you to write your queries in `.sql` files while still enjoying the great developer experience of Prisma Client. You can write the code you're comfortable with and benefit from fully-typed inputs and outputs. diff --git a/apps/docs/content/docs/orm/prisma-client/using-raw-sql/raw-queries.mdx b/apps/docs/content/docs/orm/prisma-client/using-raw-sql/raw-queries.mdx index 65402c1bc6..7cd439372d 100644 --- a/apps/docs/content/docs/orm/prisma-client/using-raw-sql/raw-queries.mdx +++ b/apps/docs/content/docs/orm/prisma-client/using-raw-sql/raw-queries.mdx @@ -332,6 +332,7 @@ $executeRawUnsafe(query: string, ...values: any[]): PrismaPromise prisma/migrations/0_init/migration.sql -``` + ```npm + npx prisma migrate diff \ + --from-empty \ + --to-schema prisma/schema.prisma \ + --script > prisma/migrations/0_init/migration.sql + ``` - Run the `prisma migrate resolve` command for each migration that should be ignored: -```npm -npx prisma migrate resolve --applied 0_init -``` + ```npm + npx prisma migrate resolve --applied 0_init + ``` This command adds the target migration to the `_prisma_migrations` table and marks it as applied. When you run `prisma migrate deploy` to apply new migrations, Prisma Migrate: diff --git a/apps/docs/content/docs/orm/prisma-migrate/workflows/development-and-production.mdx b/apps/docs/content/docs/orm/prisma-migrate/workflows/development-and-production.mdx index 973f62b916..9543aa40c7 100644 --- a/apps/docs/content/docs/orm/prisma-migrate/workflows/development-and-production.mdx +++ b/apps/docs/content/docs/orm/prisma-migrate/workflows/development-and-production.mdx @@ -93,11 +93,10 @@ This command: - Compares applied migrations against the migration history and **warns** if any migrations have been modified: - ```bash - WARNING The following migrations have been modified since they were applied: - 20210313140442_favorite_colors - ``` - + ```bash + WARNING The following migrations have been modified since they were applied: + 20210313140442_favorite_colors + ``` - Applies pending migrations The `migrate deploy` command: diff --git a/apps/docs/content/docs/orm/prisma-migrate/workflows/generating-down-migrations.mdx b/apps/docs/content/docs/orm/prisma-migrate/workflows/generating-down-migrations.mdx index db5a658d38..c07d19e56b 100644 --- a/apps/docs/content/docs/orm/prisma-migrate/workflows/generating-down-migrations.mdx +++ b/apps/docs/content/docs/orm/prisma-migrate/workflows/generating-down-migrations.mdx @@ -79,12 +79,14 @@ You will need to create the down migration first, before creating the correspond ``` - Generate the SQL file for the down migration. To do this, you will use `migrate diff` to make a comparison: + - from the newly edited schema - to the state of the schema after the last migration and output this to a SQL script, `down.sql`. There are two potential options for specifying the 'to' state: + - Using `--to-migrations`: this makes a comparison to the state of the migrations given in the migrations directory. This is the preferred option, as it is more robust. To use this option, run: ```npm diff --git a/apps/docs/content/docs/orm/prisma-migrate/workflows/patching-and-hotfixing.mdx b/apps/docs/content/docs/orm/prisma-migrate/workflows/patching-and-hotfixing.mdx index d9420b679b..50fde1d6b3 100644 --- a/apps/docs/content/docs/orm/prisma-migrate/workflows/patching-and-hotfixing.mdx +++ b/apps/docs/content/docs/orm/prisma-migrate/workflows/patching-and-hotfixing.mdx @@ -239,6 +239,7 @@ This will mark the failed migration called 'Unique' in the migrations table on y Your local migration history now yields the same result as the state your production environment is in. You can now continue using the already known `migrate dev` /`migrate deploy` workflow. + ## Prisma Migrate and PgBouncer You might see the following error if you attempt to run Prisma Migrate commands in an environment that uses PgBouncer for connection pooling: diff --git a/apps/docs/content/docs/orm/prisma-migrate/workflows/prototyping-your-schema.mdx b/apps/docs/content/docs/orm/prisma-migrate/workflows/prototyping-your-schema.mdx index 6fba169806..668d5d57ae 100644 --- a/apps/docs/content/docs/orm/prisma-migrate/workflows/prototyping-your-schema.mdx +++ b/apps/docs/content/docs/orm/prisma-migrate/workflows/prototyping-your-schema.mdx @@ -13,14 +13,13 @@ The Prisma CLI has a dedicated command for prototyping schemas: [`db push`](/orm - Introspects the database to infer and executes the changes required to make your database schema reflect the state of your Prisma schema. - Does not automatically trigger generators (for example, Prisma Client). You need to manually invoke `prisma generate` after making schema changes. - If `db push` anticipates that the changes could result in data loss, it will: - - Throw an error - - Require the `--accept-data-loss` option if you still want to make the changes + - Throw an error + - Require the `--accept-data-loss` option if you still want to make the changes :::info[Notes] - -- `db push` does not interact with or rely on migrations. The migrations table `_prisma_migrations` will not be created or updated, and no migration files will be generated. -- When working with PlanetScale, we recommend that you use `db push` instead of `migrate`. For details refer to our Getting started documentation, either [Start from scratch guide](/prisma-orm/quickstart/planetscale) or [Add to existing project guide](/prisma-orm/add-to-existing-project/planetscale) depending on your situation. - ::: + - `db push` does not interact with or rely on migrations. The migrations table `_prisma_migrations` will not be created or updated, and no migration files will be generated. + - When working with PlanetScale, we recommend that you use `db push` instead of `migrate`. For details refer to our Getting started documentation, either [Start from scratch guide](/prisma-orm/quickstart/planetscale) or [Add to existing project guide](/prisma-orm/add-to-existing-project/planetscale) depending on your situation. +::: ## Choosing `db push` or Prisma Migrate @@ -50,103 +49,103 @@ Yes, you can [use `db push` and Prisma Migrate together in your development work The following scenario demonstrates how to use `db push` to synchronize a new schema with an empty database, and evolve that schema - including what happens when `db push` detects that a change will result in data loss. -- Create a first draft of your schema: - - ```prisma title="schema.prisma" - generator client { - provider = "prisma-client" - output = "./generated" - } - - datasource db { - provider = "postgresql" - } - - model User { - id Int @id @default(autoincrement()) - name String - jobTitle String - posts Post[] - profile Profile? - } - - model Profile { - id Int @id @default(autoincrement()) - biograpy String // Intentional typo! - userId Int @unique - user User @relation(fields: [userId], references: [id]) - } - - model Post { - id Int @id @default(autoincrement()) - title String - published Boolean @default(true) - content String @db.VarChar(500) - authorId Int - author User @relation(fields: [authorId], references: [id]) - categories Category[] - } - - model Category { - id Int @id @default(autoincrement()) - name String @db.VarChar(50) - posts Post[] - - @@unique([name]) - } - ``` - -- Use `db push` to push the initial schema to the database: - - ```npm - npx prisma db push - ``` - -- Create some example content: - - ```ts title="main.ts" - const add = await prisma.user.create({ - data: { - name: "Eloise", - jobTitle: "Programmer", - posts: { - create: { - title: "How to create a MySQL database", - content: "Some content", +- Create a first draft of your schema: + + ```prisma title="schema.prisma" + generator client { + provider = "prisma-client" + output = "./generated" + } + + datasource db { + provider = "postgresql" + } + + model User { + id Int @id @default(autoincrement()) + name String + jobTitle String + posts Post[] + profile Profile? + } + + model Profile { + id Int @id @default(autoincrement()) + biograpy String // Intentional typo! + userId Int @unique + user User @relation(fields: [userId], references: [id]) + } + + model Post { + id Int @id @default(autoincrement()) + title String + published Boolean @default(true) + content String @db.VarChar(500) + authorId Int + author User @relation(fields: [authorId], references: [id]) + categories Category[] + } + + model Category { + id Int @id @default(autoincrement()) + name String @db.VarChar(50) + posts Post[] + + @@unique([name]) + } + ``` + +- Use `db push` to push the initial schema to the database: + + ```npm + npx prisma db push + ``` + +- Create some example content: + + ```ts title="main.ts" + const add = await prisma.user.create({ + data: { + name: "Eloise", + jobTitle: "Programmer", + posts: { + create: { + title: "How to create a MySQL database", + content: "Some content", + }, }, }, - }, - }); - ``` + }); + ``` -- Make an additive change - for example, create a new required field: +- Make an additive change - for example, create a new required field: - ```prisma highlight=6;add title="schema.prisma" - model Post { - id Int @id @default(autoincrement()) - title String - description String // [!code ++] - published Boolean @default(true) - content String @db.VarChar(500) - authorId Int - author User @relation(fields: [authorId], references: [id]) - categories Category[] - } - ``` + ```prisma highlight=6;add title="schema.prisma" + model Post { + id Int @id @default(autoincrement()) + title String + description String // [!code ++] + published Boolean @default(true) + content String @db.VarChar(500) + authorId Int + author User @relation(fields: [authorId], references: [id]) + categories Category[] + } + ``` -- Push the changes: +- Push the changes: - ```npm - npx prisma db push - ``` + ```npm + npx prisma db push + ``` - `db push` will fail because you cannot add a required field to a table with existing content unless you provide a default value. + `db push` will fail because you cannot add a required field to a table with existing content unless you provide a default value. -- Reset **all data** in your database and re-apply migrations. +- Reset **all data** in your database and re-apply migrations. - ```npm - npx prisma migrate reset - ``` + ```npm + npx prisma migrate reset + ``` :::info[Note] Unlike Prisma Migrate, `db push` does not generate migrations that you can modify to preserve data, and is therefore best suited for prototyping in a development environment. @@ -154,15 +153,15 @@ Unlike Prisma Migrate, `db push` does not generate migrations that you can modif - Continue to evolve your schema until it reaches a relatively stable state. -- Initialize a migration history: +- Initialize a migration history: - ```npm - npx prisma migrate dev --name initial-state - ``` + ```npm + npx prisma migrate dev --name initial-state + ``` - The steps taken to reach the initial prototype are not preserved - `db push` does not generate a history. + The steps taken to reach the initial prototype are not preserved - `db push` does not generate a history. -- Push your migration history and Prisma schema to source control (e.g. Git). +- Push your migration history and Prisma schema to source control (e.g. Git). At this point, the final draft of your prototyping is preserved in a migration and can be pushed to other environments (testing, production, or other members of your team). @@ -172,93 +171,93 @@ The following scenario demonstrates how to use `db push` to prototype a change t - Check out the latest Prisma schema and migration history: - ```prisma title="schema.prisma" - generator client { - provider = "prisma-client" - output = "./generated" - } - - datasource db { - provider = "postgresql" - } - - model User { - id Int @id @default(autoincrement()) - name String - jobTitle String - posts Post[] - profile Profile? - } - - model Profile { - id Int @id @default(autoincrement()) - biograpy String // Intentional typo! - userId Int @unique - user User @relation(fields: [userId], references: [id]) - } - - model Post { - id Int @id @default(autoincrement()) - title String - published Boolean @default(true) - content String @db.VarChar(500) - authorId Int - author User @relation(fields: [authorId], references: [id]) - categories Category[] - } - - model Category { - id Int @id @default(autoincrement()) - name String @db.VarChar(50) - posts Post[] - - @@unique([name]) - } - ``` + ```prisma title="schema.prisma" + generator client { + provider = "prisma-client" + output = "./generated" + } + + datasource db { + provider = "postgresql" + } + + model User { + id Int @id @default(autoincrement()) + name String + jobTitle String + posts Post[] + profile Profile? + } + + model Profile { + id Int @id @default(autoincrement()) + biograpy String // Intentional typo! + userId Int @unique + user User @relation(fields: [userId], references: [id]) + } + + model Post { + id Int @id @default(autoincrement()) + title String + published Boolean @default(true) + content String @db.VarChar(500) + authorId Int + author User @relation(fields: [authorId], references: [id]) + categories Category[] + } + + model Category { + id Int @id @default(autoincrement()) + name String @db.VarChar(50) + posts Post[] + + @@unique([name]) + } + ``` - Prototype your new feature, which can involve any number of steps. For example, you might: - - Create a `tags String[]` field, then run `db push` - - Change the field type to `tags Tag[]` and add a new model named `Tag`, then run `db push` - - Change your mind and restore the original `tags String[]` field, then call `db push` - - Make a manual change to the `tags` field in the database - for example, adding a constraint - - After experimenting with several solutions, the final schema change looks like this: - - ```prisma title="schema.prisma" - model Post { - id Int @id @default(autoincrement()) - title String - description String - published Boolean @default(true) - content String @db.VarChar(500) - authorId Int - author User @relation(fields: [authorId], references: [id]) - categories Category[] - tags String[] - } - ``` + - Create a `tags String[]` field, then run `db push` + - Change the field type to `tags Tag[]` and add a new model named `Tag`, then run `db push` + - Change your mind and restore the original `tags String[]` field, then call `db push` + - Make a manual change to the `tags` field in the database - for example, adding a constraint + + After experimenting with several solutions, the final schema change looks like this: + + ```prisma title="schema.prisma" + model Post { + id Int @id @default(autoincrement()) + title String + description String + published Boolean @default(true) + content String @db.VarChar(500) + authorId Int + author User @relation(fields: [authorId], references: [id]) + categories Category[] + tags String[] + } + ``` - To create a migration that adds the new `tags` field, run the `migrate dev` command: - ```npm - npx prisma migrate dev --name added-tags - ``` + ```npm + npx prisma migrate dev --name added-tags + ``` - Prisma Migrate will prompt you to reset because the changes you made manually and with `db push` while prototyping are not part of the migration history: + Prisma Migrate will prompt you to reset because the changes you made manually and with `db push` while prototyping are not part of the migration history: - ```bash - √ Drift detected: Your database schema is not in sync with your migration history. + ```bash + √ Drift detected: Your database schema is not in sync with your migration history. - We need to reset the PostgreSQL database "prototyping" at "localhost:5432". - ``` + We need to reset the PostgreSQL database "prototyping" at "localhost:5432". + ``` -:::warning -This will result in total data loss. -::: + :::warning + This will result in total data loss. + ::: -```npm -npx prisma migrate reset -``` + ```npm + npx prisma migrate reset + ``` - Prisma Migrate replays the existing migration history, generates a new migration based on your schema changes, and applies those changes to the database. diff --git a/apps/docs/content/docs/orm/prisma-migrate/workflows/squashing-migrations.mdx b/apps/docs/content/docs/orm/prisma-migrate/workflows/squashing-migrations.mdx index 4c62703ecf..fe984d2d3c 100644 --- a/apps/docs/content/docs/orm/prisma-migrate/workflows/squashing-migrations.mdx +++ b/apps/docs/content/docs/orm/prisma-migrate/workflows/squashing-migrations.mdx @@ -83,17 +83,17 @@ Then follow these steps, either on your `main` branch or on a newly checked out - Create a new empty directory in the `./prisma/migrations` directory. In this guide this will be called `000000000000_squashed_migrations`. Inside this, add a new empty `migration.sql` file. -:::info -We name the migration `000000000000_squashed_migrations` with all the leading zeroes because we want it to be the first migration in the migrations directory. Migrate runs the migrations in the directory in lexicographic (alphabetical) order. This is why it generates migrations with the date and time as a prefix when you use `migrate dev`. You can give the migration another name, as long as it it sorts lower than later migrations, for example `0_squashed` or `202207180000_squashed`. + :::info + We name the migration `000000000000_squashed_migrations` with all the leading zeroes because we want it to be the first migration in the migrations directory. Migrate runs the migrations in the directory in lexicographic (alphabetical) order. This is why it generates migrations with the date and time as a prefix when you use `migrate dev`. You can give the migration another name, as long as it it sorts lower than later migrations, for example `0_squashed` or `202207180000_squashed`. -::: + ::: - Create a single migration that takes you: - from an empty database - to the current state of the production database schema as described in your `./prisma/schema.prisma` file - and outputs this to the `migration.sql` file created above - You can do this using the `migrate diff` command. From the root directory of your project, run the following command: + You can do this using the `migrate diff` command. From the root directory of your project, run the following command: ```npm npx prisma migrate diff \ @@ -104,7 +104,7 @@ npx prisma migrate diff \ - Mark this migration as having been applied on production, to prevent it from being run there: - You can do this using the [`migrate resolve`](/orm/reference/prisma-cli-reference#migrate-resolve) command to mark the migration in the `000000000000_squashed_migrations` directory as already applied: + You can do this using the [`migrate resolve`](/orm/reference/prisma-cli-reference#migrate-resolve) command to mark the migration in the `000000000000_squashed_migrations` directory as already applied: ```npm npx prisma migrate resolve \ diff --git a/apps/docs/content/docs/orm/prisma-migrate/workflows/troubleshooting.mdx b/apps/docs/content/docs/orm/prisma-migrate/workflows/troubleshooting.mdx index 323bee9264..a0999ce52f 100644 --- a/apps/docs/content/docs/orm/prisma-migrate/workflows/troubleshooting.mdx +++ b/apps/docs/content/docs/orm/prisma-migrate/workflows/troubleshooting.mdx @@ -69,11 +69,11 @@ If you made manual changes to the database that you want to keep, you can: - Introspect the database: -```npm -npx prisma db pull -``` + ```npm + npx prisma db pull + ``` -Prisma will update your schema with the changes made directly in the database. + Prisma will update your schema with the changes made directly in the database. - Generate a new migration to include the introspected changes in your migration history: @@ -110,13 +110,12 @@ npx prisma migrate reset - Delete the `migration.sql` file. - Modify the schema - for example, add a default value to the mandatory field. - Migrate: - `npm -npx prisma migrate dev -` - Prisma Migrate will prompt you to reset the database and re-apply all migrations. + ```npm + npx prisma migrate dev + ``` + Prisma Migrate will prompt you to reset the database and re-apply all migrations. - If something interrupted the migration process, reset the database: - ```npm npx prisma migrate reset ``` diff --git a/apps/docs/content/docs/orm/prisma-migrate/workflows/unsupported-database-features.mdx b/apps/docs/content/docs/orm/prisma-migrate/workflows/unsupported-database-features.mdx index a31d5dfedf..84b8b0b8d3 100644 --- a/apps/docs/content/docs/orm/prisma-migrate/workflows/unsupported-database-features.mdx +++ b/apps/docs/content/docs/orm/prisma-migrate/workflows/unsupported-database-features.mdx @@ -2,7 +2,7 @@ title: Unsupported database features (Prisma Migrate) description: How to include unsupported database features for projects that use Prisma Migrate url: /orm/prisma-migrate/workflows/unsupported-database-features -metaTitle: "Prisma Migrate: Unsupported database features" +metaTitle: 'Prisma Migrate: Unsupported database features' metaDescription: How to include unsupported database features for projects that use Prisma Migrate. --- diff --git a/apps/docs/content/docs/orm/prisma-schema/data-model/database-mapping.mdx b/apps/docs/content/docs/orm/prisma-schema/data-model/database-mapping.mdx index bd8471f441..1280815955 100644 --- a/apps/docs/content/docs/orm/prisma-schema/data-model/database-mapping.mdx +++ b/apps/docs/content/docs/orm/prisma-schema/data-model/database-mapping.mdx @@ -103,6 +103,7 @@ You can optionally use the `map` argument to explicitly define the **underlying When introspecting a database, the `map` argument will _only_ be rendered in the schema if the name _differs_ from Prisma ORM's [default constraint naming convention for indexes and constraints](#prisma-orms-default-naming-conventions-for-indexes-and-constraints). + ### Use cases for named constraints Some use cases for explicitly named constraints include: diff --git a/apps/docs/content/docs/orm/prisma-schema/data-model/indexes.mdx b/apps/docs/content/docs/orm/prisma-schema/data-model/indexes.mdx index a1d9a54325..37937907d1 100644 --- a/apps/docs/content/docs/orm/prisma-schema/data-model/indexes.mdx +++ b/apps/docs/content/docs/orm/prisma-schema/data-model/indexes.mdx @@ -32,6 +32,7 @@ You can configure indexes, unique constraints, and primary key constraints with - Available on the `@id`, `@@id`, `@unique`, `@@unique` and `@@index` attributes - Supported in all databases + ### Configuring the length of indexes with `length` (MySQL) The `length` argument is specific to MySQL and allows you to define indexes and constraints on columns of `String` and `Byte` types. For these types, MySQL requires you to specify a maximum length for the subpart of the value to be indexed in cases where the full value would exceed MySQL's limits for index sizes. See [the MySQL documentation](https://dev.mysql.com/doc/refman/8.0/en/innodb-limits.html) for more details. @@ -87,6 +88,7 @@ The `sort` argument allows you to specify the order that the entries of the inde - In PostgreSQL, sort order can only be specified on indexes, not on unique constraints - In SQL Server, sort order is supported on all constraints and indexes including `@id` and `@@id` + For example, in MySQL/MariaDB, the following table using a descending unique constraint: ```sql @@ -711,3 +713,4 @@ model Post { @@fulltext([title(sort: Desc), content]) } ``` + diff --git a/apps/docs/content/docs/orm/prisma-schema/data-model/models.mdx b/apps/docs/content/docs/orm/prisma-schema/data-model/models.mdx index 31092c702e..78fda3dbcb 100644 --- a/apps/docs/content/docs/orm/prisma-schema/data-model/models.mdx +++ b/apps/docs/content/docs/orm/prisma-schema/data-model/models.mdx @@ -1,9 +1,9 @@ --- title: Models -description: "Learn about the concepts for building your data model with Prisma: Models, scalar types, enums, attributes, functions, IDs, default values and more" +description: 'Learn about the concepts for building your data model with Prisma: Models, scalar types, enums, attributes, functions, IDs, default values and more' url: /orm/prisma-schema/data-model/models metaTitle: Models -metaDescription: "Learn about the concepts for building your data model with Prisma: Models, scalar types, enums, attributes, functions, IDs, default values and more." +metaDescription: 'Learn about the concepts for building your data model with Prisma: Models, scalar types, enums, attributes, functions, IDs, default values and more.' --- The data model definition part of the [Prisma schema](/orm/prisma-schema/overview) defines your application models (also called **Prisma models**). Models: diff --git a/apps/docs/content/docs/orm/prisma-schema/data-model/relations/index.mdx b/apps/docs/content/docs/orm/prisma-schema/data-model/relations/index.mdx index c63ee62b3f..cb85698288 100644 --- a/apps/docs/content/docs/orm/prisma-schema/data-model/relations/index.mdx +++ b/apps/docs/content/docs/orm/prisma-schema/data-model/relations/index.mdx @@ -1,9 +1,9 @@ --- title: Relations -description: "A relation is a connection between two models in the Prisma schema. This page explains how you can define one-to-one, one-to-many and many-to-many relations in Prisma" +description: 'A relation is a connection between two models in the Prisma schema. This page explains how you can define one-to-one, one-to-many and many-to-many relations in Prisma' url: /orm/prisma-schema/data-model/relations metaTitle: Relations -metaDescription: "A relation is a connection between two models in the Prisma schema. This page explains how you can define one-to-one, one-to-many and many-to-many relations in Prisma." +metaDescription: 'A relation is a connection between two models in the Prisma schema. This page explains how you can define one-to-one, one-to-many and many-to-many relations in Prisma.' --- A relation is a _connection_ between two models in the Prisma schema. For example, there is a one-to-many relation between `User` and `Post` because one user can have many blog posts: diff --git a/apps/docs/content/docs/orm/prisma-schema/data-model/relations/referential-actions.mdx b/apps/docs/content/docs/orm/prisma-schema/data-model/relations/referential-actions.mdx index e6421b08e4..48998e0efa 100644 --- a/apps/docs/content/docs/orm/prisma-schema/data-model/relations/referential-actions.mdx +++ b/apps/docs/content/docs/orm/prisma-schema/data-model/relations/referential-actions.mdx @@ -3,7 +3,7 @@ title: Referential actions description: Referential actions let you define the update and delete behavior of related models on the database level url: /orm/prisma-schema/data-model/relations/referential-actions metaTitle: Special rules for referential actions in SQL Server and MongoDB -metaDescription: "Circular references or multiple cascade paths can cause validation errors on Microsoft SQL Server and MongoDB. Since the database does not handle these situations out of the box, learn how to solve this problem." +metaDescription: 'Circular references or multiple cascade paths can cause validation errors on Microsoft SQL Server and MongoDB. Since the database does not handle these situations out of the box, learn how to solve this problem.' --- Referential actions determine what happens to a record when your application deletes or updates a related record. They are defined in the [`@relation`](/orm/reference/prisma-schema-reference#relation) attribute and map to foreign key constraints in the database. @@ -38,12 +38,13 @@ If you do not specify a referential action, Prisma ORM [uses a default](#referen + ## Available referential actions Prisma ORM supports five referential actions: - **[`Cascade`](#cascade)** - Deletes/updates cascade to related records -- **[`Restrict`](#restrict)** - Prevents deletion/update if related records exist +- **[`Restrict`](#restrict)** - Prevents deletion/update if related records exist - **[`NoAction`](#noaction)** - Similar to Restrict, behavior varies by database - **[`SetNull`](#setnull)** - Sets foreign key to NULL (requires optional relation) - **[`SetDefault`](#setdefault)** - Sets foreign key to default value diff --git a/apps/docs/content/docs/orm/prisma-schema/data-model/relations/relation-mode.mdx b/apps/docs/content/docs/orm/prisma-schema/data-model/relations/relation-mode.mdx index 542f275d76..44c38a38f0 100644 --- a/apps/docs/content/docs/orm/prisma-schema/data-model/relations/relation-mode.mdx +++ b/apps/docs/content/docs/orm/prisma-schema/data-model/relations/relation-mode.mdx @@ -88,6 +88,7 @@ datasource db { } ``` + For relational databases, the available options are: - `foreignKeys`: this handles relations in the database with foreign keys. This is the default option for all relational database connectors and is active if no `relationMode` is explicitly set in the `datasource` block. diff --git a/apps/docs/content/docs/orm/prisma-schema/data-model/unsupported-database-features.mdx b/apps/docs/content/docs/orm/prisma-schema/data-model/unsupported-database-features.mdx index 4de83be8a8..d917f69499 100644 --- a/apps/docs/content/docs/orm/prisma-schema/data-model/unsupported-database-features.mdx +++ b/apps/docs/content/docs/orm/prisma-schema/data-model/unsupported-database-features.mdx @@ -2,7 +2,7 @@ title: Unsupported database features (Prisma Schema) description: How to support database features that do not have an equivalent syntax in Prisma Schema Language url: /orm/prisma-schema/data-model/unsupported-database-features -metaTitle: "Prisma schema: Unsupported database features" +metaTitle: 'Prisma schema: Unsupported database features' metaDescription: How to support database features that do not have an equivalent syntax in Prisma Schema Language. --- diff --git a/apps/docs/content/docs/orm/prisma-schema/overview/data-sources.mdx b/apps/docs/content/docs/orm/prisma-schema/overview/data-sources.mdx index 65b5f3c2da..a906a86072 100644 --- a/apps/docs/content/docs/orm/prisma-schema/overview/data-sources.mdx +++ b/apps/docs/content/docs/orm/prisma-schema/overview/data-sources.mdx @@ -19,6 +19,7 @@ A Prisma schema can only have _one_ data source. However, you can: - [Override the database connection when creating your `PrismaClient`](/orm/reference/prisma-client-reference) - [Specify a different **database** for Prisma Migrate's shadow database if you are working with cloud-hosted development databases](/orm/prisma-migrate/understanding-prisma-migrate/shadow-database#cloud-hosted-shadow-databases-must-be-created-manually) + ## Securing database connections Some data source `provider`s allow you to configure your connection with SSL/TLS **by specifying certificate locations in your connection configuration**. diff --git a/apps/docs/content/docs/orm/prisma-schema/overview/generators.mdx b/apps/docs/content/docs/orm/prisma-schema/overview/generators.mdx index afce2d24c8..405b53dc04 100644 --- a/apps/docs/content/docs/orm/prisma-schema/overview/generators.mdx +++ b/apps/docs/content/docs/orm/prisma-schema/overview/generators.mdx @@ -215,6 +215,7 @@ For using types in your frontend (i.e. code that runs in the browser). - Contains all model and enum types and values. - Provides access to various utilities like `Prisma.JsonNull` and `Prisma.Decimal`. + Example: ```ts title="src/index.ts" diff --git a/apps/docs/content/docs/orm/prisma-schema/overview/index.mdx b/apps/docs/content/docs/orm/prisma-schema/overview/index.mdx index 4df16829c1..bca8342209 100644 --- a/apps/docs/content/docs/orm/prisma-schema/overview/index.mdx +++ b/apps/docs/content/docs/orm/prisma-schema/overview/index.mdx @@ -32,6 +32,7 @@ The following is an example of a Prisma Schema that specifies: - A data model definition with two models (with one relation) and one `enum` - Several [native data type attributes](/orm/prisma-schema/data-model/models#native-types-mapping) (`@db.VarChar(255)`, `@db.ObjectId`) + ```prisma tab="Relational Databases" datasource db { provider = "postgresql" @@ -67,6 +68,7 @@ enum Role { } ``` + ```prisma tab="MongoDB" datasource db { provider = "mongodb" @@ -102,6 +104,7 @@ enum Role { } ``` + ## Syntax Prisma Schema files are written in Prisma Schema Language (PSL). See the [data sources](/orm/prisma-schema/overview/data-sources), [generators](/orm/prisma-schema/overview/generators), [data model definition](/orm/prisma-schema/data-model/models) and of course [Prisma Schema API reference](/orm/reference/prisma-schema-reference) pages for details and examples. diff --git a/apps/docs/content/docs/orm/prisma-schema/overview/location.mdx b/apps/docs/content/docs/orm/prisma-schema/overview/location.mdx index 32d99e7fd0..e8d3cdefa6 100644 --- a/apps/docs/content/docs/orm/prisma-schema/overview/location.mdx +++ b/apps/docs/content/docs/orm/prisma-schema/overview/location.mdx @@ -50,7 +50,10 @@ Run prisma generate to generate Prisma Client. - + diff --git a/apps/docs/content/docs/orm/reference/connection-urls.mdx b/apps/docs/content/docs/orm/reference/connection-urls.mdx index 0419d6ec3c..c990159387 100644 --- a/apps/docs/content/docs/orm/reference/connection-urls.mdx +++ b/apps/docs/content/docs/orm/reference/connection-urls.mdx @@ -1,9 +1,9 @@ --- title: Connection URLs -description: "Learn about the format and syntax Prisma ORM uses for defining database connection URLs for PostgreSQL, MySQL and SQLite" +description: 'Learn about the format and syntax Prisma ORM uses for defining database connection URLs for PostgreSQL, MySQL and SQLite' url: /orm/reference/connection-urls metaTitle: Connection URLs (Reference) -metaDescription: "Learn about the format and syntax Prisma ORM uses for defining database connection URLs for PostgreSQL, MySQL and SQLite." +metaDescription: 'Learn about the format and syntax Prisma ORM uses for defining database connection URLs for PostgreSQL, MySQL and SQLite.' --- Prisma ORM needs a connection URL to be able to connect to your database, e.g. when sending queries with [Prisma Client](/orm/prisma-client/setup-and-configuration/introduction) or when changing the database schema with [Prisma Migrate](/orm/prisma-migrate). @@ -81,7 +81,7 @@ When connecting via Prisma Accelerate, the connection string doesn't require a u ```ts title="prisma.config.ts" export default defineConfig({ datasource: { - url: "prisma+postgres://accelerate.prisma-data.net/?api_key=API_KEY", + url: "prisma+postgres://accelerate.prisma-data.net/?api_key=API_KEY" }, }); ``` @@ -91,7 +91,7 @@ In this snippet, `API_KEY` is a placeholder for the API key you are receiving wh ```ts title="prisma.config.ts" export default defineConfig({ datasource: { - url: "prisma+postgres://accelerate.prisma-data.net/?api_key=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcGlfa2V5IjoiMGNkZTFlMjQtNzhiYi00NTY4LTkyM2EtNWUwOTEzZWUyNjU1IiwidGVuYW50X2lkIjoiNzEyZWRlZTc1Y2U2MDk2ZjI4NDg3YjE4NWMyYzA2OTNhNGMxNzJkMjhhOWFlNGUwZTYxNWE4NWIxZWY1YjBkMCIsImludGVybmFsX3NlY3JldCI6IjA4MzQ2Y2RlLWI5ZjktNDQ4Yy04NThmLTMxNjg4ODEzNmEzZCJ9.N1Za6q6NfInzHvRkud6Ojt_-RFg18a0601vdYWGKOrk", + url: "prisma+postgres://accelerate.prisma-data.net/?api_key=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcGlfa2V5IjoiMGNkZTFlMjQtNzhiYi00NTY4LTkyM2EtNWUwOTEzZWUyNjU1IiwidGVuYW50X2lkIjoiNzEyZWRlZTc1Y2U2MDk2ZjI4NDg3YjE4NWMyYzA2OTNhNGMxNzJkMjhhOWFlNGUwZTYxNWE4NWIxZWY1YjBkMCIsImludGVybmFsX3NlY3JldCI6IjA4MzQ2Y2RlLWI5ZjktNDQ4Yy04NThmLTMxNjg4ODEzNmEzZCJ9.N1Za6q6NfInzHvRkud6Ojt_-RFg18a0601vdYWGKOrk" }, }); ``` @@ -103,7 +103,7 @@ The connection string for connecting to a [local Prisma Postgres](/postgres/data ```ts title="prisma.config.ts" export default defineConfig({ datasource: { - url: "prisma+postgres://accelerate.prisma-data.net/?api_key=API_KEY", + url: "prisma+postgres://accelerate.prisma-data.net/?api_key=API_KEY" }, }); ``` @@ -115,7 +115,7 @@ However, in this case the `API_KEY` doesn't provide authentication details. Inst ```ts title="prisma.config.ts" export default defineConfig({ datasource: { - url: "postgresql://janedoe:mypassword@localhost:5432/mydb?schema=sample", + url: "postgresql://janedoe:mypassword@localhost:5432/mydb?schema=sample" }, }); ``` @@ -125,7 +125,7 @@ export default defineConfig({ ```ts title="prisma.config.ts" export default defineConfig({ datasource: { - url: "mysql://janedoe:mypassword@localhost:3306/mydb", + url: "mysql://janedoe:mypassword@localhost:3306/mydb" }, }); ``` @@ -135,7 +135,7 @@ export default defineConfig({ ```ts title="prisma.config.ts" export default defineConfig({ datasource: { - url: "sqlserver://localhost:1433;initial catalog=sample;user=sa;password=mypassword;", + url: "sqlserver://localhost:1433;initial catalog=sample;user=sa;password=mypassword;" }, }); ``` @@ -145,7 +145,7 @@ export default defineConfig({ ```ts title="prisma.config.ts" export default defineConfig({ datasource: { - url: "file:./dev.db", + url: "file:./dev.db" }, }); ``` @@ -155,7 +155,7 @@ export default defineConfig({ ```ts title="prisma.config.ts" export default defineConfig({ datasource: { - url: "postgresql://janedoe:mypassword@localhost:26257/mydb?schema=public", + url: "postgresql://janedoe:mypassword@localhost:26257/mydb?schema=public" }, }); ``` diff --git a/apps/docs/content/docs/orm/reference/error-reference.mdx b/apps/docs/content/docs/orm/reference/error-reference.mdx index a430351b9b..6addd0391e 100644 --- a/apps/docs/content/docs/orm/reference/error-reference.mdx +++ b/apps/docs/content/docs/orm/reference/error-reference.mdx @@ -1,9 +1,9 @@ --- title: Error Reference -description: "Prisma Client, Migrate, and Introspection error codes" +description: 'Prisma Client, Migrate, and Introspection error codes' url: /orm/reference/error-reference metaTitle: Errors -metaDescription: "Prisma Client, Migrate, Introspection error message reference" +metaDescription: 'Prisma Client, Migrate, Introspection error message reference' --- For more information about how to work with exceptions and error codes, see [Handling exceptions and errors](/orm/prisma-client/debugging-and-troubleshooting/handling-exceptions-and-errors). @@ -116,7 +116,7 @@ Prisma Client throws a `PrismaClientValidationError` exception if validation fai #### `P1012` -:::info +:::info If you get error code P1012 after you upgrade Prisma ORM to version 4.0.0 or later, see the [version 4.0.0 upgrade guide](/guides/upgrade-prisma-orm/v4#update-schema). A schema that was valid before version 4.0.0 might be invalid in version 4.0.0 and later. The upgrade guide explains how to update your schema to make it valid. ::: diff --git a/apps/docs/content/docs/orm/reference/errors/index.mdx b/apps/docs/content/docs/orm/reference/errors/index.mdx index 1db91fb040..54c8228a91 100644 --- a/apps/docs/content/docs/orm/reference/errors/index.mdx +++ b/apps/docs/content/docs/orm/reference/errors/index.mdx @@ -3,7 +3,7 @@ title: Prisma Error Reference description: Common Prisma ORM errors and how to troubleshoot them url: /orm/reference/errors metaTitle: Errors -metaDescription: "Prisma Client, Migrate, Introspection error message reference" +metaDescription: 'Prisma Client, Migrate, Introspection error message reference' --- This section provides information about common errors you might encounter when using Prisma ORM and how to resolve them. diff --git a/apps/docs/content/docs/orm/reference/preview-features/client-preview-features.mdx b/apps/docs/content/docs/orm/reference/preview-features/client-preview-features.mdx index a9d198e24a..3542c423a0 100644 --- a/apps/docs/content/docs/orm/reference/preview-features/client-preview-features.mdx +++ b/apps/docs/content/docs/orm/reference/preview-features/client-preview-features.mdx @@ -14,16 +14,16 @@ For more information, see [ORM releases and maturity levels](/orm/more/releases) The following [Preview](/orm/more/releases#preview) feature flags are available for Prisma Client and Prisma schema: -| Feature | Released into Preview | Feedback issue | -| -------------------------------------------------------------------------- | :------------------------------------------------------------- | :-------------------------------------------------------------------: | -| [`views`](/orm/prisma-schema/data-model/views) | [4.9.0](https://github.com/prisma/prisma/releases/tag/4.9.0) | [Submit feedback](https://github.com/prisma/prisma/issues/17335) | -| `relationJoins` | [5.7.0](https://github.com/prisma/prisma/releases/tag/5.7.0) | [Submit feedback](https://github.com/prisma/prisma/discussions/22288) | -| `nativeDistinct` | [5.7.0](https://github.com/prisma/prisma/releases/tag/5.7.0) | [Submit feedback](https://github.com/prisma/prisma/discussions/22287) | -| `typedSql` | [5.19.0](https://github.com/prisma/prisma/releases/tag/5.19.0) | [Submit feedback](https://github.com/prisma/prisma/discussions/25106) | -| `strictUndefinedChecks` | [5.20.0](https://github.com/prisma/prisma/releases/tag/5.20.0) | [Submit feedback](https://github.com/prisma/prisma/discussions/25271) | +| Feature | Released into Preview | Feedback issue | +| ----------------------------------------------------------------------- | :------------------------------------------------------------- | :-------------------------------------------------------------------: | +| [`views`](/orm/prisma-schema/data-model/views) | [4.9.0](https://github.com/prisma/prisma/releases/tag/4.9.0) | [Submit feedback](https://github.com/prisma/prisma/issues/17335) | +| `relationJoins` | [5.7.0](https://github.com/prisma/prisma/releases/tag/5.7.0) | [Submit feedback](https://github.com/prisma/prisma/discussions/22288) | +| `nativeDistinct` | [5.7.0](https://github.com/prisma/prisma/releases/tag/5.7.0) | [Submit feedback](https://github.com/prisma/prisma/discussions/22287) | +| `typedSql` | [5.19.0](https://github.com/prisma/prisma/releases/tag/5.19.0) | [Submit feedback](https://github.com/prisma/prisma/discussions/25106) | +| `strictUndefinedChecks` | [5.20.0](https://github.com/prisma/prisma/releases/tag/5.20.0) | [Submit feedback](https://github.com/prisma/prisma/discussions/25271) | | [`fullTextSearchPostgres`](/v6/orm/prisma-client/queries/full-text-search) | [6.0.0](https://github.com/prisma/prisma/releases/tag/6.0.0) | [Submit feedback](https://github.com/prisma/prisma/issues/25773) | -| `shardKeys` | [6.10.0](https://pris.ly/release/6.10.0) | [Submit feedback](https://github.com/prisma/prisma/issues/) | -| [`partialIndexes`](/orm/prisma-schema/data-model/indexes) | [7.4.0](https://pris.ly/release/7.4.0) | [Submit feedback](https://github.com/prisma/prisma/issues/6974) | +| `shardKeys` | [6.10.0](https://pris.ly/release/6.10.0) | [Submit feedback](https://github.com/prisma/prisma/issues/) | +| [`partialIndexes`](/orm/prisma-schema/data-model/indexes) | [7.4.0](https://pris.ly/release/7.4.0) | [Submit feedback](https://github.com/prisma/prisma/issues/6974) | To enable a Preview feature, [add the feature flag to the `generator` block](#enabling-a-prisma-client-preview-feature) in your `schema.prisma` file. [Share your feedback on all Preview features on GitHub](https://github.com/prisma/prisma/issues/3108). @@ -65,7 +65,7 @@ In the list below, you can find a history of Prisma Client and Prisma schema fea | [`clientExtensions`](/orm/prisma-client/client-extensions) | [4.7.0](https://github.com/prisma/prisma/releases/tag/4.7.0) | [4.16.0](https://github.com/prisma/prisma/releases/tag/4.16.0) | | [`filteredRelationCount`](/orm/prisma-client/queries/aggregation-grouping-summarizing#filter-the-relation-count) | [4.3.0](https://github.com/prisma/prisma/releases/tag/4.3.0) | [4.16.0](https://github.com/prisma/prisma/releases/tag/4.16.0) | | [`tracing`](/orm/prisma-client/observability-and-logging/opentelemetry-tracing) | [4.2.0](https://github.com/prisma/prisma/releases/tag/4.2.0) | [6.1.0](https://github.com/prisma/prisma/releases/tag/6.1.0) | -| [`orderByNulls`](/v6/orm/prisma-client/queries/filtering-and-sorting#sort-with-null-records-first-or-last) | [4.1.0](https://github.com/prisma/prisma/releases/tag/4.1.0) | [4.16.0](https://github.com/prisma/prisma/releases/tag/4.16.0) | +| [`orderByNulls`](/v6/orm/prisma-client/queries/filtering-and-sorting#sort-with-null-records-first-or-last) | [4.1.0](https://github.com/prisma/prisma/releases/tag/4.1.0) | [4.16.0](https://github.com/prisma/prisma/releases/tag/4.16.0) | | [`referentialIntegrity`](/orm/prisma-schema/data-model/relations/relation-mode) | [3.1.1](https://github.com/prisma/prisma/releases/tag/3.1.1) | [4.7.0](https://github.com/prisma/prisma/releases/tag/4.7.0) | | [`interactiveTransactions`](/orm/prisma-client/queries/transactions#interactive-transactions) | [2.29.0](https://github.com/prisma/prisma/releases/tag/2.29.0) | [4.7.0](https://github.com/prisma/prisma/releases/tag/4.7.0)
with Prisma Accelerate [5.1.1](https://github.com/prisma/prisma/releases/tag/5.1.1) | | [`extendedIndexes`](/orm/prisma-schema/data-model/indexes) | [3.5.0](https://github.com/prisma/prisma/releases/tag/3.5.0) | [4.0.0](https://github.com/prisma/prisma/releases/tag/4.0.0) | @@ -77,7 +77,7 @@ In the list below, you can find a history of Prisma Client and Prisma schema fea | [`namedConstraints`](/orm/prisma-schema/data-model/database-mapping#constraint-and-index-names) | [2.29.0](https://github.com/prisma/prisma/releases/tag/2.29.0) | [3.0.1](https://github.com/prisma/prisma/releases/tag/3.0.1) | | [`referentialActions`](/orm/prisma-schema/data-model/relations/referential-actions) | [2.26.0](https://github.com/prisma/prisma/releases/tag/2.26.0) | [3.0.1](https://github.com/prisma/prisma/releases/tag/3.0.1) | | [`orderByAggregateGroup`](/orm/prisma-client/queries/aggregation-grouping-summarizing#order-by-aggregate-group) | [2.21.0](https://github.com/prisma/prisma/releases/tag/2.21.0) | [3.0.1](https://github.com/prisma/prisma/releases/tag/3.0.1) | -| [`orderByRelation`](/v6/orm/prisma-client/queries/filtering-and-sorting#sort-by-relation) | [2.16.0](https://github.com/prisma/prisma/releases/tag/2.16.0)
aggregates in [2.19.0](https://github.com/prisma/prisma/releases/tag/2.19.0) | [3.0.1](https://github.com/prisma/prisma/releases/tag/3.0.1) | +| [`orderByRelation`](/v6/orm/prisma-client/queries/filtering-and-sorting#sort-by-relation) | [2.16.0](https://github.com/prisma/prisma/releases/tag/2.16.0)
aggregates in [2.19.0](https://github.com/prisma/prisma/releases/tag/2.19.0) | [3.0.1](https://github.com/prisma/prisma/releases/tag/3.0.1) | | [`selectRelationCount`](/orm/prisma-client/queries/aggregation-grouping-summarizing#count-relations) | [2.20.0](https://github.com/prisma/prisma/releases/tag/2.20.0) | [3.0.1](https://github.com/prisma/prisma/releases/tag/3.0.1) | | `napi` | [2.20.0](https://github.com/prisma/prisma/releases/tag/2.20.0) | [3.0.1](https://github.com/prisma/prisma/releases/tag/3.0.1) | | [`groupBy`](/orm/reference/prisma-client-reference#groupby) | [2.14.0](https://github.com/prisma/prisma/releases/tag/2.14.0) | [2.20.0](https://github.com/prisma/prisma/releases/tag/2.20.0) | @@ -87,6 +87,6 @@ In the list below, you can find a history of Prisma Client and Prisma schema fea | [`transactionApi`](/orm/prisma-client/queries/transactions#the-transaction-api) | [2.1.0](https://github.com/prisma/prisma/releases/tag/2.1.0) | [2.11.0](https://github.com/prisma/prisma/releases/tag/2.11.0) | | [`connectOrCreate`](/orm/reference/prisma-client-reference#connectorcreate) | [2.1.0](https://github.com/prisma/prisma/releases/tag/2.1.0) | [2.11.0](https://github.com/prisma/prisma/releases/tag/2.11.0) | | [`atomicNumberOperations`](/orm/reference/prisma-client-reference#atomic-number-operations) | [2.6.0](https://github.com/prisma/prisma/releases/tag/2.6.0) | [2.10.0](https://github.com/prisma/prisma/releases/tag/2.10.0) | -| [`insensitiveFilters` (PostgreSQL)](/v6/orm/prisma-client/queries/filtering-and-sorting#case-insensitive-filtering) | [2.5.0](https://github.com/prisma/prisma/releases/tag/2.5.0) | [2.8.0](https://github.com/prisma/prisma/releases/tag/2.8.0) | +| [`insensitiveFilters` (PostgreSQL)](/v6/orm/prisma-client/queries/filtering-and-sorting#case-insensitive-filtering) | [2.5.0](https://github.com/prisma/prisma/releases/tag/2.5.0) | [2.8.0](https://github.com/prisma/prisma/releases/tag/2.8.0) | | [`aggregateApi`](/orm/prisma-client/queries/aggregation-grouping-summarizing#aggregate) | [2.2.0](https://github.com/prisma/prisma/releases/tag/2.2.0) | [2.5.0](https://github.com/prisma/prisma/releases/tag/2.5.0) | | [`distinct`](/orm/reference/prisma-client-reference#distinct) | [2.3.0](https://github.com/prisma/prisma/releases/tag/2.3.0) | [2.5.0](https://github.com/prisma/prisma/releases/tag/2.5.0) | diff --git a/apps/docs/content/docs/orm/reference/prisma-cli-reference.mdx b/apps/docs/content/docs/orm/reference/prisma-cli-reference.mdx index 761b1bf074..36420d2f95 100644 --- a/apps/docs/content/docs/orm/reference/prisma-cli-reference.mdx +++ b/apps/docs/content/docs/orm/reference/prisma-cli-reference.mdx @@ -1,9 +1,9 @@ --- title: Prisma CLI reference -description: "This page gives an overview of all available Prisma CLI commands, explains their options and shows numerous usage examples" +description: 'This page gives an overview of all available Prisma CLI commands, explains their options and shows numerous usage examples' url: /orm/reference/prisma-cli-reference metaTitle: Prisma CLI reference -metaDescription: "This page gives an overview of all available Prisma CLI commands, explains their options and shows numerous usage examples." +metaDescription: 'This page gives an overview of all available Prisma CLI commands, explains their options and shows numerous usage examples.' --- This document describes the Prisma CLI commands, arguments, and options. @@ -91,7 +91,7 @@ By default, the project sets up a [local Prisma Postgres](/postgres/database/loc #### Arguments | Argument | Required | Description | Default | -| ------------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | +| ------------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | | `--datasource-provider` | No | Specifies the value for the `provider` field in the `datasource` block. Options are `prisma+postgres`, `sqlite`, `postgresql`, `mysql`, `sqlserver`, `mongodb` and `cockroachdb`. | `postgresql` | | `--db` | No | Shorthand syntax for `--datasource-provider prisma+postgres`; creates a new [Prisma Postgres](/postgres) instance. Requires authentication in the [PDP Console](https://console.prisma.io). | | | `--prompt` (or `--vibe`) | No | Scaffolds a Prisma schema based on the prompt and deploys it to a fresh Prisma Postgres instance. Requires authentication in the [PDP Console](https://console.prisma.io). | | @@ -393,14 +393,14 @@ generator client { #### Options -| Option | Required | Description | Default | -| ------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | +| Option | Required | Description | Default | +| ------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | `--data-proxy` | No | The `generate` command will generate Prisma Client for use with [Prisma Accelerate](/accelerate) prior to Prisma 5.0.0. Mutually exclusive with `--accelerate` and `--no-engine`. | | `--accelerate` | No | The `generate` command will generate Prisma Client for use with [Prisma Accelerate](/accelerate). Mutually exclusive with `--data-proxy` and `--no-engine`. Available in Prisma 5.1.0 and later. | | `--no-engine` | No | The `generate` command will generate Prisma Client without an accompanied engine for use with [Prisma Accelerate](/accelerate). Mutually exclusive with `--data-proxy` and `--accelerate`. Available in Prisma ORM 5.2.0 and later. | -| `--no-hints` | No | The `generate` command will generate Prisma Client without usage hints, surveys or info banners being printed to the terminal. Available in Prisma ORM 5.16.0 and later. | -| `--allow-no-models` | No | The `generate` command will generate Prisma Client without generating any models. | -| `--watch` | No | The `generate` command will continue to watch the `schema.prisma` file and re-generate Prisma Client on file changes. | +| `--no-hints` | No | The `generate` command will generate Prisma Client without usage hints, surveys or info banners being printed to the terminal. Available in Prisma ORM 5.16.0 and later. | +| `--allow-no-models` | No | The `generate` command will generate Prisma Client without generating any models. | +| `--watch` | No | The `generate` command will continue to watch the `schema.prisma` file and re-generate Prisma Client on file changes. | :::warning @@ -681,14 +681,14 @@ The `dev` command starts a [local Prisma Postgres](/postgres/database/local-deve ### Arguments -| Argument | Required | Description | Default | -| --------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | +| Argument | Required | Description | Default | +| --------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | | `--name` (or `-n`) | No | Enables targeting a specific database instance. [Learn more](/postgres/database/local-development#using-different-local-prisma-postgres-instances). | `default` | -| `--port` (or `-p`) | No | Main port number the local Prisma Postgres HTTP server will listen on. | `51213` | -| `--db-port` (or `-P`) | No | Port number the local Prisma Postgres database server will listen on. | `51214` | -| `--shadow-db-port` | No | Port number the shadow database server will listen on. | `51215` | -| `--detach` (or `-d`) | No | Run the server in the background. | `false` | -| `--debug` | No | Enable debug logging. | `false` | +| `--port` (or `-p`) | No | Main port number the local Prisma Postgres HTTP server will listen on. | `51213` | +| `--db-port` (or `-P`) | No | Port number the local Prisma Postgres database server will listen on. | `51214` | +| `--shadow-db-port` | No | Port number the shadow database server will listen on. | `51215` | +| `--detach` (or `-d`) | No | Run the server in the background. | `false` | +| `--debug` | No | Enable debug logging. | `false` | ### Examples @@ -812,8 +812,8 @@ npx prisma dev rm mydb* # removes all DBs starting with `mydb` #### Arguments -| Argument | Required | Description | Default | -| --------- | -------- | ------------------------------------------------------------------------------------------------------------------ | ------- | +| Argument | Required | Description | Default | +| --------- | -------- | -------------------------------------------------------------------------------------------------------------------- | ------- | | `--force` | No | Stops any running servers before removing them. Without this flag, the command will fail if any server is running. | `false` | :::note @@ -1498,7 +1498,7 @@ One of the following `--from-...` options is required: | `--from-empty` | Assume that the data model you are migrating from is empty | | | `--from-schema` | Path to a Prisma schema file, uses the data model for the diff | | | `--from-migrations` | Path to the Prisma Migrate migrations directory | Not supported in MongoDB | -| `--from-config-datasource` | Use the datasource from the Prisma config file | Prisma v7+ | +| `--from-config-datasource` | Use the datasource from the Prisma config file | Prisma v7+ | One of the following `--to-...` options is required: @@ -1507,7 +1507,7 @@ One of the following `--to-...` options is required: | `--to-empty` | Assume that the data model you are migrating to is empty | | | `--to-schema` | Path to a Prisma schema file, uses the data model for the diff | | | `--to-migrations` | Path to the Prisma Migrate migrations directory | Not supported in MongoDB | -| `--to-config-datasource` | Use the datasource from the Prisma config file | Prisma v7+ | +| `--to-config-datasource` | Use the datasource from the Prisma config file | Prisma v7+ | Other options: diff --git a/apps/docs/content/docs/orm/reference/prisma-client-reference.mdx b/apps/docs/content/docs/orm/reference/prisma-client-reference.mdx index 3887a114d9..0552094498 100644 --- a/apps/docs/content/docs/orm/reference/prisma-client-reference.mdx +++ b/apps/docs/content/docs/orm/reference/prisma-client-reference.mdx @@ -377,10 +377,10 @@ Sets default [transaction options](/orm/prisma-client/queries/transactions#trans #### Options -| Option | Description | -| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `maxWait` | The maximum amount of time Prisma Client will wait to acquire a transaction from the database. The default value is 2 seconds. | -| `timeout` | The maximum amount of time the interactive transaction can run before being canceled and rolled back. The default value is 5 seconds. | +| Option | Description | +| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `maxWait` | The maximum amount of time Prisma Client will wait to acquire a transaction from the database. The default value is 2 seconds. | +| `timeout` | The maximum amount of time the interactive transaction can run before being canceled and rolled back. The default value is 5 seconds. | | `isolationLevel` | Sets the [transaction isolation level](/orm/prisma-client/queries/transactions#transaction-isolation-level). By default this is set to the value currently configured in your database. The available levels can vary depending on the database you use. | #### Example @@ -414,13 +414,13 @@ Use model queries to perform CRUD operations on your models. See also: [CRUD](/o #### Options -| Name | Example type (`User`) | Required | Description | -| ---------------------- | ------------------------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `where` | `UserWhereUniqueInput` | **Yes** | Wraps all fields of a model so that a record can be selected ([learn more](#filter-on-non-unique-fields-with-userwhereuniqueinput)). | -| `select` | `XOR` | No | [Specifies which properties to include](/v6/orm/prisma-client/queries/select-fields) on the returned object. | -| `include` | `XOR` | No | [Specifies which relations should be eagerly loaded](/orm/prisma-client/queries/relation-queries) on the returned object. | -| `omit` | `XOR` | No | Specifies which properties to exclude on the returned object. Excludes specified fields from the result | -| `relationLoadStrategy` | `'join'` or `'query'` | No | **Default: `join`**. [Load strategy](/orm/prisma-client/queries/relation-queries#relation-load-strategies-preview) for relations. Requires `relationJoins` preview feature. | +| Name | Example type (`User`) | Required | Description | +| ---------------------- | ------------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `where` | `UserWhereUniqueInput` | **Yes** | Wraps all fields of a model so that a record can be selected ([learn more](#filter-on-non-unique-fields-with-userwhereuniqueinput)). | +| `select` | `XOR` | No | [Specifies which properties to include](/v6/orm/prisma-client/queries/select-fields) on the returned object. | +| `include` | `XOR` | No | [Specifies which relations should be eagerly loaded](/orm/prisma-client/queries/relation-queries) on the returned object. | +| `omit` | `XOR` | No | Specifies which properties to exclude on the returned object. Excludes specified fields from the result | +| `relationLoadStrategy` | `'join'` or `'query'` | No | **Default: `join`**. [Load strategy](/orm/prisma-client/queries/relation-queries#relation-load-strategies-preview) for relations. Requires `relationJoins` preview feature. | #### Return type @@ -545,7 +545,7 @@ await prisma.user.findUniqueOrThrow({ | Name | Example type (`User`) | Required | Description | | ---------------------- | ----------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `select` | `XOR` | No | [Specifies which properties to include](/v6/orm/prisma-client/queries/select-fields) on the returned object. | +| `select` | `XOR` | No | [Specifies which properties to include](/v6/orm/prisma-client/queries/select-fields) on the returned object. | | `include` | `XOR` | No | [Specifies which relations should be eagerly loaded](/orm/prisma-client/queries/relation-queries) on the returned object. | | `omit` | `XOR` | No | Specifies which properties to exclude on the returned object. Excludes specified fields from the result. | | `relationLoadStrategy` | `'join'` or `'query'` | No | **Default: `join`**. [Load strategy](/orm/prisma-client/queries/relation-queries#relation-load-strategies-preview) for relations. Requires `relationJoins` preview feature. | @@ -637,7 +637,7 @@ main(); | Name | Type | Required | Description | | ---------------------- | ------------------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `select` | `XOR` | No | [Specifies which properties to include](/v6/orm/prisma-client/queries/select-fields) on the returned object. | +| `select` | `XOR` | No | [Specifies which properties to include](/v6/orm/prisma-client/queries/select-fields) on the returned object. | | `include` | `XOR` | No | [Specifies which relations should be eagerly loaded](/orm/prisma-client/queries/relation-queries) on the returned object. | | `omit` | `XOR` | No | Specifies which properties to exclude on the returned object. Excludes specified fields from the result | | `relationLoadStrategy` | `'join'` or `'query'` | No | **Default: `join`**. [Load strategy](/orm/prisma-client/queries/relation-queries#relation-load-strategies-preview) for relations. Requires `relationJoins` preview feature. | @@ -677,7 +677,7 @@ const user = await prisma.user.findMany({ | Name | Type | Required | Description | | ---------------------- | -------------------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `data` | `XOR`UserUncheckedCreateInput>` | **Yes** | Wraps all the model fields in a type so that they can be provided when creating new records. It also includes relation fields which lets you perform (transactional) nested inserts. Fields that are marked as optional or have default values in the datamodel are optional. | -| [`select`](#select) | `XOR` | No | [Specifies which properties to include](/v6/orm/prisma-client/queries/select-fields) on the returned object. | +| [`select`](#select) | `XOR` | No | [Specifies which properties to include](/v6/orm/prisma-client/queries/select-fields) on the returned object. | | [`include`](#include) | `XOR` | No | [Specifies which relations should be eagerly loaded](/orm/prisma-client/queries/relation-queries) on the returned object. | | [`omit`](#omit) | `XOR` | No | Specifies which properties to exclude on the returned object. Excludes specified fields from the result | | `relationLoadStrategy` | `'join'` or `'query'` | No | **Default: `join`**. [Load strategy](/orm/prisma-client/queries/relation-queries#relation-load-strategies-preview) for relations. Requires `relationJoins` preview feature. | @@ -768,14 +768,14 @@ prisma:query COMMIT #### Options -| Name | Type | Required | Description | -| ---------------------- | ------------------------------------------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `data` | `XOR`UserUncheckedUpdateInput>` | **Yes** | Wraps all the fields of the model so that they can be provided when updating an existing record. Fields that are marked as optional or have default values in the datamodel are optional. | -| `where` | `UserWhereUniqueInput` | **Yes** | Wraps all fields of a model so that a record can be selected ([learn more](#filter-on-non-unique-fields-with-userwhereuniqueinput)). | -| [`select`](#select) | `XOR` | No | [Specifies which properties to include](/v6/orm/prisma-client/queries/select-fields) on the returned object. | -| [`include`](#include) | `XOR` | No | [Specifies which relations should be eagerly loaded](/orm/prisma-client/queries/relation-queries) on the returned object. | -| [`omit`](#omit) | `XOR` | No | Specifies which properties to exclude on the returned object. Excludes specified fields from the result. | -| `relationLoadStrategy` | `'join'` or `'query'` | No | **Default: `join`**. [Load strategy](/orm/prisma-client/queries/relation-queries#relation-load-strategies-preview) for relations. Requires `relationJoins` preview feature. | +| Name | Type | Required | Description | +| ---------------------- | ------------------------------------------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data` | `XOR`UserUncheckedUpdateInput>` | **Yes** | Wraps all the fields of the model so that they can be provided when updating an existing record. Fields that are marked as optional or have default values in the datamodel are optional. | +| `where` | `UserWhereUniqueInput` | **Yes** | Wraps all fields of a model so that a record can be selected ([learn more](#filter-on-non-unique-fields-with-userwhereuniqueinput)). | +| [`select`](#select) | `XOR` | No | [Specifies which properties to include](/v6/orm/prisma-client/queries/select-fields) on the returned object. | +| [`include`](#include) | `XOR` | No | [Specifies which relations should be eagerly loaded](/orm/prisma-client/queries/relation-queries) on the returned object. | +| [`omit`](#omit) | `XOR` | No | Specifies which properties to exclude on the returned object. Excludes specified fields from the result. | +| `relationLoadStrategy` | `'join'` or `'query'` | No | **Default: `join`**. [Load strategy](/orm/prisma-client/queries/relation-queries#relation-load-strategies-preview) for relations. Requires `relationJoins` preview feature. | #### Return type @@ -820,8 +820,8 @@ This section covers the usage of the `upsert()` operation. To learn about using | ---------------------- | ------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `create` | `XOR`UserUncheckedCreateInput>` | **Yes** | Wraps all the fields of the model so that they can be provided when creating new records. It also includes relation fields which lets you perform (transactional) nested inserts. Fields that are marked as optional or have default values in the datamodel are optional. | | `update` | `XOR`UserUncheckedUpdateInput>` | **Yes** | Wraps all the fields of the model so that they can be provided when updating an existing record. Fields that are marked as optional or have default values in the datamodel are optional. | -| `where` | `UserWhereUniqueInput` | **Yes** | Wraps all fields of a model so that a record can be selected ([learn more](#filter-on-non-unique-fields-with-userwhereuniqueinput)). | -| [`select`](#select) | `XOR` | No | [Specifies which properties to include](/v6/orm/prisma-client/queries/select-fields) on the returned object. | +| `where` | `UserWhereUniqueInput` | **Yes** | Wraps all fields of a model so that a record can be selected ([learn more](#filter-on-non-unique-fields-with-userwhereuniqueinput)). | +| [`select`](#select) | `XOR` | No | [Specifies which properties to include](/v6/orm/prisma-client/queries/select-fields) on the returned object. | | [`include`](#include) | `XOR` | No | [Specifies which relations should be eagerly loaded](/orm/prisma-client/queries/relation-queries) on the returned object. | | [`omit`](#omit) | `XOR` | No | Specifies which properties to exclude on the returned object. Excludes specified fields from the result | | `relationLoadStrategy` | `'join'` or `'query'` | No | **Default: `join`**. [Load strategy](/orm/prisma-client/queries/relation-queries#relation-load-strategies-preview) for relations. Requires `relationJoins` preview feature. | @@ -1021,13 +1021,13 @@ To delete records that match a certain criteria, use [`deleteMany`](#deletemany) #### Options -| Name | Type | Required | Description | -| ---------------------- | ------------------------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `where` | `UserWhereUniqueInput` | **Yes** | Wraps all fields of a model so that a record can be selected ([learn more](#filter-on-non-unique-fields-with-userwhereuniqueinput)). | -| [`select`](#select) | `XOR` | No | [Specifies which properties to include](/v6/orm/prisma-client/queries/select-fields) on the returned object. | -| [`include`](#include) | `XOR` | No | [Specifies which relations should be eagerly loaded](/orm/prisma-client/queries/relation-queries) on the returned object. | -| [`omit`](#omit) | `XOR` | No | Specifies which properties to exclude on the returned object. Excludes specified fields from the result | -| `relationLoadStrategy` | `'join'` or `'query'` | No | **Default: `join`**. [Load strategy](/orm/prisma-client/queries/relation-queries#relation-load-strategies-preview) for relations. Requires `relationJoins` preview feature. | +| Name | Type | Required | Description | +| ---------------------- | ------------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `where` | `UserWhereUniqueInput` | **Yes** | Wraps all fields of a model so that a record can be selected ([learn more](#filter-on-non-unique-fields-with-userwhereuniqueinput)). | +| [`select`](#select) | `XOR` | No | [Specifies which properties to include](/v6/orm/prisma-client/queries/select-fields) on the returned object. | +| [`include`](#include) | `XOR` | No | [Specifies which relations should be eagerly loaded](/orm/prisma-client/queries/relation-queries) on the returned object. | +| [`omit`](#omit) | `XOR` | No | Specifies which properties to exclude on the returned object. Excludes specified fields from the result | +| `relationLoadStrategy` | `'join'` or `'query'` | No | **Default: `join`**. [Load strategy](/orm/prisma-client/queries/relation-queries#relation-load-strategies-preview) for relations. Requires `relationJoins` preview feature. | #### Return type @@ -1117,7 +1117,7 @@ const users = await prisma.user.createMany({ | Name | Type | Required | Description | | --------------------- | --------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `data` | `Enumerable` | **Yes** | Wraps all the model fields in a type so that they can be provided when creating new records. Fields that are marked as optional or have default values in the datamodel are optional. | -| [`select`](#select) | `XOR` | No | [Specifies which properties to include](/v6/orm/prisma-client/queries/select-fields) on the returned objects. | +| [`select`](#select) | `XOR` | No | [Specifies which properties to include](/v6/orm/prisma-client/queries/select-fields) on the returned objects. | | [`omit`](#omit) | `XOR` | No | Specifies which properties to exclude on the returned objects. Excludes specified fields from the result. Mutually exclusive with `select`. | | [`include`](#include) | `XOR` | No | [Specifies which relations should be eagerly loaded](/orm/prisma-client/queries/relation-queries) on the returned objects. | | `skipDuplicates?` | `boolean` | No | Do not insert records with unique fields or ID fields that already exist. Only supported by databases that support [`ON CONFLICT DO NOTHING`](https://www.postgresql.org/docs/9.5/sql-insert.html#SQL-ON-CONFLICT). This excludes MongoDB and SQLServer | @@ -1424,9 +1424,9 @@ const minMaxAge = await prisma.user.aggregate({ ```json { - "_count": { "_all": 29 }, - "_max": { "profileViews": 90 }, - "_min": { "profileViews": 0 } + _count: { _all: 29 }, + _max: { profileViews: 90 }, + _min: { profileViews: 0 } } ``` @@ -1555,8 +1555,8 @@ const result = await prisma.user.findUnique({ ```json { - "name": "Alice", - "profileViews": 0 + name: "Alice", + profileViews: 0 } ``` @@ -2510,6 +2510,7 @@ generator client { } ``` + See [Preview Features](/orm/reference/preview-features/client-preview-features#preview-features-promoted-to-general-availability) for more details. ## Nested queries @@ -2530,13 +2531,13 @@ A nested `create` query adds a new related record or set of records to a parent ```ts highlight=5;normal const user = await prisma.user.create({ data: { - email: "alice@prisma.io", + email: 'alice@prisma.io', profile: { - create: { bio: "Hello World" }, + create: { bio: 'Hello World' }, }, }, }); -``` +```` ##### Create a new `Profile` record with a new `User` record @@ -4974,7 +4975,11 @@ The following example shows how you can create type-checked input for the `creat ```ts import { Prisma } from "../prisma/generated/client"; -const createUserAndPostInput = (name: string, email: string, postTitle: string) => +const createUserAndPostInput = ( + name: string, + email: string, + postTitle: string, +) => ({ name, email, @@ -5205,15 +5210,10 @@ await prisma.user.update({ where: { id: 1 }, data: { to_one: { - update: { - where: { - /* WhereInput */ - }, - data: { field: "updated" }, - }, - }, - }, -}); + update: { where: { /* WhereInput */ }, data: { field: "updated" } } + } + } +}) ``` ##### Nested upsert example @@ -5224,19 +5224,13 @@ await prisma.user.update({ data: { to_one: { upsert: { - where: { - /* WhereInput */ - }, - create: { - /* CreateInput */ - }, - update: { - /* UpdateInput */ - }, - }, - }, - }, -}); + where: { /* WhereInput */ }, + create: { /* CreateInput */ }, + update: { /* UpdateInput */ }, + } + } + } +}) ``` ##### Nested disconnect example @@ -5246,12 +5240,10 @@ await prisma.user.update({ where: { id: 1 }, data: { to_one: { - disconnect: { - /* WhereInput */ - }, - }, - }, -}); + disconnect: { /* WhereInput */ } + } + } +}) ``` ##### Nested delete example @@ -5261,12 +5253,10 @@ await prisma.user.update({ where: { id: 1 }, data: { to_one: { - delete: { - /* WhereInput */ - }, - }, - }, -}); + delete: { /* WhereInput */ } + } + } +}) ``` ## `PrismaPromise` behavior diff --git a/apps/docs/content/docs/orm/reference/prisma-schema-reference.mdx b/apps/docs/content/docs/orm/reference/prisma-schema-reference.mdx index f4105e0ddc..ad9a4bd4c0 100644 --- a/apps/docs/content/docs/orm/reference/prisma-schema-reference.mdx +++ b/apps/docs/content/docs/orm/reference/prisma-schema-reference.mdx @@ -14,12 +14,12 @@ Defines a [data source](/orm/prisma-schema/overview/data-sources) in the Prisma A `datasource` block accepts the following fields: -| Name | Required | Type | Description | -| -------------- | -------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | -| `provider` | **Yes** | String (`postgresql`, `mysql`, `sqlite`, `sqlserver`, `mongodb`, `cockroachdb`) | Specifies the database connector to use. | -| `relationMode` | No | String (`foreignKeys`, `prisma`) | Sets whether [referential integrity](/orm/prisma-schema/data-model/relations/relation-mode) is enforced by foreign keys or by Prisma. | -| `schemas` | No | Array of strings | List of database schemas to include ([multi-schema](/orm/prisma-schema/data-model/multi-schema) support, PostgreSQL and SQL Server). | -| `extensions` | No | Array of extension names | [PostgreSQL extensions](/orm/prisma-schema/postgresql-extensions) to enable. | +| Name | Required | Type | Description | +| -------------- | -------- | ------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| `provider` | **Yes** | String (`postgresql`, `mysql`, `sqlite`, `sqlserver`, `mongodb`, `cockroachdb`) | Specifies the database connector to use. | +| `relationMode` | No | String (`foreignKeys`, `prisma`) | Sets whether [referential integrity](/orm/prisma-schema/data-model/relations/relation-mode) is enforced by foreign keys or by Prisma. | +| `schemas` | No | Array of strings | List of database schemas to include ([multi-schema](/orm/prisma-schema/data-model/multi-schema) support, PostgreSQL and SQL Server). | +| `extensions` | No | Array of extension names | [PostgreSQL extensions](/orm/prisma-schema/postgresql-extensions) to enable. | Connection URLs (`url`, `directUrl`, `shadowDatabaseUrl`) are configured in [`prisma.config.ts`](/orm/reference/prisma-config-reference#datasourceurl), not in the schema file. @@ -145,14 +145,14 @@ This is the default generator for Prisma ORM 6.x and earlier versions. Learn mor A `generator` block accepts the following fields: -| Name | Required | Type | Description | -| :---------------- | :------- | :--------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `provider` | **Yes** | `prisma-client-js` | Describes which [generator](/orm/prisma-schema/overview/generators) to use. This can point to a file that implements a generator or specify a built-in generator directly. | +| Name | Required | Type | Description | +| :---------------- | :------- | :--------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `provider` | **Yes** | `prisma-client-js` | Describes which [generator](/orm/prisma-schema/overview/generators) to use. This can point to a file that implements a generator or specify a built-in generator directly. | | `output` | No | String (file path) | Determines the location for the generated client, [learn more](/orm/reference/prisma-schema-reference#fields-for-prisma-client-provider). **Default**: `node_modules/.prisma/client` | -| `previewFeatures` | No | List of Enums | Use intellisense to see list of currently available Preview features (`Ctrl+Space` in Visual Studio Code) **Default**: none | -| `engineType` | No | Enum (`library` or `binary`) | Defines the query engine type to download and use. **Default**: `library` | -| `binaryTargets` | No | List of Enums (see below) | Specify the OS on which the Prisma Client will run to ensure compatibility of the query engine. **Default**: `native` | -| `moduleFormat` | No | Enum (`cjs` or `esm`) | Defines the module format of the generated Prisma Client. This field is available only with `prisma-client` generator. | +| `previewFeatures` | No | List of Enums | Use intellisense to see list of currently available Preview features (`Ctrl+Space` in Visual Studio Code) **Default**: none | +| `engineType` | No | Enum (`library` or `binary`) | Defines the query engine type to download and use. **Default**: `library` | +| `binaryTargets` | No | List of Enums (see below) | Specify the OS on which the Prisma Client will run to ensure compatibility of the query engine. **Default**: `native` | +| `moduleFormat` | No | Enum (`cjs` or `esm`) | Defines the module format of the generated Prisma Client. This field is available only with `prisma-client` generator. | :::note[important] @@ -173,9 +173,9 @@ The `prisma-client` generator is the default generator in Prisma ORM 7. A `generator` block accepts the following fields: | Name | Required | Type | Description | -| :----------------------- | :------- | :----------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| :----------------------- | :------- | :----------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `provider` | **Yes** | `prisma-client` | Describes which [generator](/orm/prisma-schema/overview/generators) to use. This can point to a file that implements a generator or specify a built-in generator directly. | -| `output` | **Yes** | String (file path) | Determines the location for the generated client, [learn more](/orm/reference/prisma-schema-reference#fields-for-prisma-client-provider). | +| `output` | **Yes** | String (file path) | Determines the location for the generated client, [learn more](/orm/reference/prisma-schema-reference#fields-for-prisma-client-provider). | | `previewFeatures` | No | List of Enums | Use intellisense to see list of currently available Preview features (`Ctrl+Space` in Visual Studio Code) **Default**: none | | `runtime` | No | Enum (`nodejs`, `deno`, `bun`, `workerd` (alias `cloudflare`), `vercel-edge` (alias `edge-light`), `react-native`) | Target runtime environment. **Default**: `nodejs` | | `moduleFormat` | No | Enum (`esm` or `cjs`) | Determines whether the generated code supports ESM (uses `import`) or CommonJS (uses `require(...)`) modules. We always recommend `esm` unless you have a good reason to use `cjs`. **Default**: Inferred from environment. | @@ -1162,12 +1162,12 @@ Defines a single-field ID on the model. #### Arguments -| Name | Required | Type | Description | -| ----------- | -------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `map` | **No** | `String` | The name of the underlying primary key constraint in the database.

Not supported for MySQL or MongoDB. | +| Name | Required | Type | Description | +| ----------- | -------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `map` | **No** | `String` | The name of the underlying primary key constraint in the database.

Not supported for MySQL or MongoDB. | | `length` | **No** | `number` | Allows you to specify a maximum length for the subpart of the value to be indexed.

MySQL only. | | `sort` | **No** | `String` | Allows you to specify in what order the entries of the ID are stored in the database. The available options are `Asc` and `Desc`.

SQL Server only. | -| `clustered` | **No** | `Boolean` | Defines whether the ID is clustered or non-clustered. Defaults to `true`.

SQL Server only. | +| `clustered` | **No** | `Boolean` | Defines whether the ID is clustered or non-clustered. Defaults to `true`.

SQL Server only. | #### Signature @@ -1401,14 +1401,14 @@ Defines a multi-field ID (composite ID) on the model. #### Arguments -| Name | Required | Type | Description | -| ----------- | -------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `fields` | **Yes** | `FieldReference[]` | A list of field names - for example, `["firstname", "lastname"]` | -| `name` | **No** | `String` | The name that Prisma Client will expose for the argument covering all fields, e.g. `fullName` in `fullName: { firstName: "First", lastName: "Last"}` | -| `map` | **No** | `String` | The name of the underlying primary key constraint in the database.

Not supported for MySQL. | +| Name | Required | Type | Description | +| ----------- | -------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `fields` | **Yes** | `FieldReference[]` | A list of field names - for example, `["firstname", "lastname"]` | +| `name` | **No** | `String` | The name that Prisma Client will expose for the argument covering all fields, e.g. `fullName` in `fullName: { firstName: "First", lastName: "Last"}` | +| `map` | **No** | `String` | The name of the underlying primary key constraint in the database.

Not supported for MySQL. | | `length` | **No** | `number` | Allows you to specify a maximum length for the subpart of the value to be indexed.

MySQL only. | | `sort` | **No** | `String` | Allows you to specify in what order the entries of the ID are stored in the database. The available options are `Asc` and `Desc`.

SQL Server only. | -| `clustered` | **No** | `Boolean` | Defines whether the ID is clustered or non-clustered. Defaults to `true`.

SQL Server only. | +| `clustered` | **No** | `Boolean` | Defines whether the ID is clustered or non-clustered. Defaults to `true`.

SQL Server only. | The name of the `fields` argument on the `@@id` attribute can be omitted: diff --git a/apps/docs/content/docs/postgres/database/backups.mdx b/apps/docs/content/docs/postgres/database/backups.mdx index 39604a26a0..1b602aae11 100644 --- a/apps/docs/content/docs/postgres/database/backups.mdx +++ b/apps/docs/content/docs/postgres/database/backups.mdx @@ -47,7 +47,6 @@ brew install postgresql@17 which pg_dump which pg_restore ``` - ```bash tab="Windows" # Download from the official PostgreSQL website: # https://www.postgresql.org/download/windows/ @@ -57,7 +56,6 @@ which pg_restore where pg_dump where pg_restore ``` - ```bash tab="Linux" sudo apt-get update sudo apt-get install postgresql-client-17 diff --git a/apps/docs/content/docs/postgres/database/connecting-to-your-database.mdx b/apps/docs/content/docs/postgres/database/connecting-to-your-database.mdx index 044a174459..01546b7c0d 100644 --- a/apps/docs/content/docs/postgres/database/connecting-to-your-database.mdx +++ b/apps/docs/content/docs/postgres/database/connecting-to-your-database.mdx @@ -42,12 +42,12 @@ DIRECT_URL="postgres://USER:PASSWORD@db.prisma.io:5432/?sslmode=require" ## Which connection to use -| If you are... | Use | Why | -| ------------------------------------------------------------------------------------------- | ---------- | -------------------------------------------------------------------- | -| Running application queries (API handlers, workers, functions) | **Pooled** | Safer under concurrency. Reuses a small set of database connections. | +| If you are... | Use | Why | +| ---------------------------------------------------------------- | ---------- | -------------------------------------------------------------------- | +| Running application queries (API handlers, workers, functions) | **Pooled** | Safer under concurrency. Reuses a small set of database connections. | | Running migrations, introspection, `pg_dump`, `pg_restore`, Prisma Studio, or admin tooling | **Direct** | These workflows need session continuity or should bypass the pooler. | -| Using `LISTEN/NOTIFY` or session-level `SET` commands | **Direct** | The pooler does not preserve session state across transactions. | -| Running queries or jobs that may exceed 10 minutes | **Direct** | Pooled connections enforce a 10-minute query timeout. | +| Using `LISTEN/NOTIFY` or session-level `SET` commands | **Direct** | The pooler does not preserve session state across transactions. | +| Running queries or jobs that may exceed 10 minutes | **Direct** | Pooled connections enforce a 10-minute query timeout. | If you swap them, the failure modes are asymmetric. Using the pooled string for migrations produces obvious errors (lock failures, prepared statement errors). Using the direct string for application traffic works at low concurrency but silently exhausts connections under production load. See [Connection pooling](/postgres/database/connection-pooling) for details. @@ -114,9 +114,7 @@ if (process.env.NODE_ENV !== "production") { - The serverless driver uses the **direct** connection string but does **not** open a TCP - connection to the database. The direct hostname is used for routing only, the transport is - HTTP/WebSocket, not PostgreSQL wire protocol. + The serverless driver uses the **direct** connection string but does **not** open a TCP connection to the database. The direct hostname is used for routing only, the transport is HTTP/WebSocket, not PostgreSQL wire protocol. diff --git a/apps/docs/content/docs/postgres/database/connection-pooling.mdx b/apps/docs/content/docs/postgres/database/connection-pooling.mdx index ee95f5c005..e2faed1b23 100644 --- a/apps/docs/content/docs/postgres/database/connection-pooling.mdx +++ b/apps/docs/content/docs/postgres/database/connection-pooling.mdx @@ -10,10 +10,10 @@ Prisma Postgres routes application traffic through a tenant-isolated PgBouncer i ## Connection limits -| | Free | Starter | Pro | Business | -| ---------------------- | ---- | ------- | --- | -------- | -| **Pooled connections** | 50 | 50 | 250 | 500 | -| **Direct connections** | 10 | 10 | 50 | 100 | +| | Free | Starter | Pro | Business | +| ---------------------- | ---- | ------- | ---- | -------- | +| **Pooled connections** | 50 | 50 | 250 | 500 | +| **Direct connections** | 10 | 10 | 50 | 100 | These are concurrent connection limits per plan. 5 direct connections are reserved for platform operations (maintenance, monitoring) and are not available to your workload. @@ -68,4 +68,4 @@ Expected behavior in transactional pool mode. The pooler returns the backend con ### Query killed after 10 minutes -Pooled connections enforce a 10-minute query timeout. Break the query into smaller batches, or run it over a direct connection where no timeout is enforced. +Pooled connections enforce a 10-minute query timeout. Break the query into smaller batches, or run it over a direct connection where no timeout is enforced. \ No newline at end of file diff --git a/apps/docs/content/docs/postgres/database/local-development.mdx b/apps/docs/content/docs/postgres/database/local-development.mdx index 58c97e0d50..cdc6adf4fd 100644 --- a/apps/docs/content/docs/postgres/database/local-development.mdx +++ b/apps/docs/content/docs/postgres/database/local-development.mdx @@ -248,19 +248,19 @@ try { The `startPrismaDevServer()` function accepts the following options: -| **Argument** | **Required** | **Description** | **Default** | -| ---------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------ | -| **`name`** | ❌ | Unique identifier for the local Prisma Postgres instance. Use distinct names if running multiple servers in parallel. | `'default'` | -| **`port`** | ❌ | Port for the Prisma engine server. Throws an error if the port is already in use. | `51213` | -| **`databasePort`** | ❌ | Port for the embedded PostgreSQL database. Used for all Prisma ORM connections. | `51214` | -| **`shadowDatabasePort`** | ❌ | Port for the shadow database used during migrations. | `51215` | -| **`persistenceMode`** | ❌ | Defines how data is persisted:
• `'stateless'` — no data is retained between runs
• `'stateful'` — data persists locally | `'stateless'` | -| **`debug`** | ❌ | Whether to enable debug logging. | `false` | -| **`dryRun`** | ❌ | Whether to run the server in dry run mode. | `false` | -| **`databaseConnectTimeoutMillis`** | ❌ | Connection timeout in milliseconds for pending database connections. Starts ticking for every new client that attempts to connect. When exceeded, the pending client connection is evicted and closed. Use with caution, as it may lead to unexpected behavior. Best used with a pool client that retries connections. | `60000` (1 minute) | -| **`databaseIdleTimeoutMillis`** | ❌ | Idle timeout in milliseconds for active database connections. Re-starts ticking after each message received on the active connection. When exceeded, the active client connection is closed, and a pending connection is promoted to active. Use with caution, as it may lead to unexpected disconnections. Best used with a pool client that can handle disconnections gracefully. Set it if you suffer from client hanging indefinitely. | Not applied by default | -| **`shadowDatabaseConnectTimeoutMillis`** | ❌ | Connection timeout in milliseconds for pending shadow database connections. | Defaults to `databaseConnectTimeoutMillis` | -| **`shadowDatabaseIdleTimeoutMillis`** | ❌ | Idle timeout in milliseconds for active shadow database connections. | Defaults to `databaseIdleTimeoutMillis` | +| **Argument** | **Required** | **Description** | **Default** | +| ------------------------ | ------------ | ------------------------------------------------------------------------------------------------------------------------------ | ------------- | +| **`name`** | ❌ | Unique identifier for the local Prisma Postgres instance. Use distinct names if running multiple servers in parallel. | `'default'` | +| **`port`** | ❌ | Port for the Prisma engine server. Throws an error if the port is already in use. | `51213` | +| **`databasePort`** | ❌ | Port for the embedded PostgreSQL database. Used for all Prisma ORM connections. | `51214` | +| **`shadowDatabasePort`** | ❌ | Port for the shadow database used during migrations. | `51215` | +| **`persistenceMode`** | ❌ | Defines how data is persisted:
• `'stateless'` — no data is retained between runs
• `'stateful'` — data persists locally | `'stateless'` | +| **`debug`** | ❌ | Whether to enable debug logging. | `false` | +| **`dryRun`** | ❌ | Whether to run the server in dry run mode. | `false` | +| **`databaseConnectTimeoutMillis`** | ❌ | Connection timeout in milliseconds for pending database connections. Starts ticking for every new client that attempts to connect. When exceeded, the pending client connection is evicted and closed. Use with caution, as it may lead to unexpected behavior. Best used with a pool client that retries connections. | `60000` (1 minute) | +| **`databaseIdleTimeoutMillis`** | ❌ | Idle timeout in milliseconds for active database connections. Re-starts ticking after each message received on the active connection. When exceeded, the active client connection is closed, and a pending connection is promoted to active. Use with caution, as it may lead to unexpected disconnections. Best used with a pool client that can handle disconnections gracefully. Set it if you suffer from client hanging indefinitely. | Not applied by default | +| **`shadowDatabaseConnectTimeoutMillis`** | ❌ | Connection timeout in milliseconds for pending shadow database connections. | Defaults to `databaseConnectTimeoutMillis` | +| **`shadowDatabaseIdleTimeoutMillis`** | ❌ | Idle timeout in milliseconds for active shadow database connections. | Defaults to `databaseIdleTimeoutMillis` | :::tip diff --git a/apps/docs/content/docs/postgres/database/query-insights.mdx b/apps/docs/content/docs/postgres/database/query-insights.mdx index 2360b31c2b..cf3eb61672 100644 --- a/apps/docs/content/docs/postgres/database/query-insights.mdx +++ b/apps/docs/content/docs/postgres/database/query-insights.mdx @@ -28,4 +28,4 @@ Query Insights is included with Prisma Postgres at no extra cost. :::info Query Insights has its own dedicated section with full documentation, examples, and setup instructions. See [Query Insights](/query-insights). -::: +::: \ No newline at end of file diff --git a/apps/docs/content/docs/postgres/database/serverless-driver.mdx b/apps/docs/content/docs/postgres/database/serverless-driver.mdx index d7fd6418f6..c6e65be8a8 100644 --- a/apps/docs/content/docs/postgres/database/serverless-driver.mdx +++ b/apps/docs/content/docs/postgres/database/serverless-driver.mdx @@ -4,7 +4,7 @@ description: Connect to Prisma Postgres from serverless and edge environments badge: early-access url: /postgres/database/serverless-driver metaTitle: Serverless driver -metaDescription: "A lightweight PostgreSQL driver for Prisma Postgres optimized for serverless and edge environments with HTTP/WebSocket support, result streaming, and minimal memory footprint." +metaDescription: 'A lightweight PostgreSQL driver for Prisma Postgres optimized for serverless and edge environments with HTTP/WebSocket support, result streaming, and minimal memory footprint.' --- The Prisma Postgres serverless driver ([`@prisma/ppg`](https://www.npmjs.com/package/@prisma/ppg)) is a lightweight client for connecting to Prisma Postgres using raw SQL. It uses HTTP and WebSocket protocols instead of traditional TCP connections, enabling database access in constrained environments where native PostgreSQL drivers cannot run. diff --git a/apps/docs/content/docs/postgres/error-reference.mdx b/apps/docs/content/docs/postgres/error-reference.mdx index 2eea5cba3e..53073d6bdd 100644 --- a/apps/docs/content/docs/postgres/error-reference.mdx +++ b/apps/docs/content/docs/postgres/error-reference.mdx @@ -2,7 +2,7 @@ title: Error reference description: Error reference documentation for Prisma Postgres url: /postgres/error-reference -metaTitle: "Prisma Postgres: Error Reference" +metaTitle: 'Prisma Postgres: Error Reference' metaDescription: Error reference documentation for Prisma Postgres. --- diff --git a/apps/docs/content/docs/postgres/faq.mdx b/apps/docs/content/docs/postgres/faq.mdx index 4f8941ea02..d939f98d4b 100644 --- a/apps/docs/content/docs/postgres/faq.mdx +++ b/apps/docs/content/docs/postgres/faq.mdx @@ -14,13 +14,13 @@ Common questions about how Prisma Postgres works, how queries are billed, and ho Yes, you can use Prisma Postgres with any database library or tool via a [direct connection](/postgres/database/connection-pooling). -You can find examples of using Prisma Postgres with various ORMs below: - +You can find examples of using Prisma Postgres with various ORMs below: - [Prisma ORM](https://github.com/prisma/prisma-examples/tree/latest/databases/prisma-postgres) - [Drizzle](https://github.com/prisma/prisma-examples/tree/latest/databases/drizzle-prisma-postgres) - [Kysely](https://github.com/prisma/prisma-examples/tree/latest/databases/kysely-prisma-postgres) - [TypeORM](https://github.com/prisma/prisma-examples/tree/latest/databases/typeorm-prisma-postgres) + ### How do I switch from GitHub login to email and password login? If you previously signed up using GitHub and want to switch to email and password login, follow these steps: @@ -259,6 +259,7 @@ Under the hood, Prisma Postgres's cache layer uses Cloudflare, which uses [Anyca You can invalidate the cache on-demand via the [`$accelerate.invalidate` API](/accelerate/reference/api-reference#accelerateinvalidate) if you're on a [paid plan](https://www.prisma.io/pricing#accelerate), or you can invalidate your entire cache, on a project level, a maximum of five times a day. This limit is set based on [your plan](https://www.prisma.io/pricing). You can manage this via the Accelerate configuration page. + ### How is Prisma Postgres's caching layer different from other caching tools, such as Redis? The caching layer of Prisma Postgres: diff --git a/apps/docs/content/docs/postgres/iac/pulumi.mdx b/apps/docs/content/docs/postgres/iac/pulumi.mdx index d70ed22635..eab4edd4c8 100644 --- a/apps/docs/content/docs/postgres/iac/pulumi.mdx +++ b/apps/docs/content/docs/postgres/iac/pulumi.mdx @@ -108,11 +108,9 @@ const connection = new prismaPostgres.Connection("connection", { name: "api-key", }); -const availableRegions = prismaPostgres - .getRegionsOutput() - .regions.apply((regions) => - regions.filter((r) => r.status === "available").map((r) => `${r.id} (${r.name})`), - ); +const availableRegions = prismaPostgres.getRegionsOutput().regions.apply((regions) => + regions.filter((r) => r.status === "available").map((r) => `${r.id} (${r.name})`) +); export const projectId = project.id; export const databaseId = database.id; diff --git a/apps/docs/content/docs/postgres/index.mdx b/apps/docs/content/docs/postgres/index.mdx index 85db6a2c87..dbfdbb4397 100644 --- a/apps/docs/content/docs/postgres/index.mdx +++ b/apps/docs/content/docs/postgres/index.mdx @@ -1,6 +1,6 @@ --- title: Prisma Postgres -description: "Connect to Prisma Postgres from Prisma ORM, serverless runtimes, and PostgreSQL clients." +description: 'Connect to Prisma Postgres from Prisma ORM, serverless runtimes, and PostgreSQL clients.' url: /postgres metaTitle: Overview | Prisma Postgres metaDescription: Learn everything you need to know about Prisma Postgres. @@ -19,10 +19,7 @@ New to Prisma Postgres? Start here. Create a temporary Prisma Postgres database in one command. - + Set up Prisma ORM and connect it to Prisma Postgres. diff --git a/apps/docs/content/docs/postgres/integrations/raycast.mdx b/apps/docs/content/docs/postgres/integrations/raycast.mdx index d1c671efdb..ff055dd5a0 100644 --- a/apps/docs/content/docs/postgres/integrations/raycast.mdx +++ b/apps/docs/content/docs/postgres/integrations/raycast.mdx @@ -38,7 +38,6 @@ Instantly create a new Prisma Postgres database with zero configuration: 3. Press Enter to create You'll receive: - - A connection string for immediate use - A direct URL for connecting to your database - A claim URL to make your database permanent diff --git a/apps/docs/content/docs/query-insights/index.mdx b/apps/docs/content/docs/query-insights/index.mdx index a328c2d16b..96ffe26ecc 100644 --- a/apps/docs/content/docs/query-insights/index.mdx +++ b/apps/docs/content/docs/query-insights/index.mdx @@ -92,7 +92,8 @@ const adapter = new PrismaPg({ export const prisma = new PrismaClient({ adapter: adapter, comments: [prismaQueryInsights()], -}); +}) + ``` This adds SQL comment annotations to queries so Query Insights can map SQL statements back to the Prisma calls that generated them. It is built on top of the [SQL comments](/orm/prisma-client/observability-and-logging/sql-comments) feature in Prisma Client. @@ -105,14 +106,14 @@ Query Insights is included with Prisma Postgres at no extra cost. You can try it Query Insights is most useful when it connects a database symptom to a concrete code change. -| Issue | What you might see | Typical fix | -| ------------------ | ------------------------------------ | ------------------------------------------- | -| N+1 queries | High query count for one request | Use nested reads, batching, or joins | -| Missing indexes | High reads relative to rows returned | Add the right index for the filter pattern | -| Over-fetching | Wide rows or large payloads | Use `select` to fetch fewer fields | -| Offset pagination | Reads grow on deeper pages | Switch to cursor pagination | -| Large nested reads | High reads and large payloads | Limit fields, limit depth, or split queries | -| Repeated queries | The same statement shape runs often | Cache or reuse results when appropriate | +| Issue | What you might see | Typical fix | +| --- | --- | --- | +| N+1 queries | High query count for one request | Use nested reads, batching, or joins | +| Missing indexes | High reads relative to rows returned | Add the right index for the filter pattern | +| Over-fetching | Wide rows or large payloads | Use `select` to fetch fewer fields | +| Offset pagination | Reads grow on deeper pages | Switch to cursor pagination | +| Large nested reads | High reads and large payloads | Limit fields, limit depth, or split queries | +| Repeated queries | The same statement shape runs often | Cache or reuse results when appropriate | ## How to use it @@ -136,7 +137,6 @@ In most cases, the next change falls into one of these buckets: ## Example A common example is an N+1 pattern: - ```ts const users = await prisma.user.findMany({ select: { id: true, name: true, email: true }, @@ -158,7 +158,6 @@ Query Insights would typically show: - More reads and latency than the route should need In this case, the likely fix is to load the related posts in one nested read: - ```ts const usersWithPosts = await prisma.user.findMany({ select: { diff --git a/apps/docs/content/docs/studio/getting-started.mdx b/apps/docs/content/docs/studio/getting-started.mdx index 492596e8c8..4f779a8ede 100644 --- a/apps/docs/content/docs/studio/getting-started.mdx +++ b/apps/docs/content/docs/studio/getting-started.mdx @@ -2,8 +2,8 @@ title: Getting Started description: Learn how to set up and use Prisma Studio to manage your database url: /studio/getting-started -metaTitle: "Get started with Prisma Studio" -metaDescription: "Learn how to install Prisma Studio, connect to your database, browse and edit records, and export to CSV or JSON. The visual database browser for Prisma projects." +metaTitle: 'Get started with Prisma Studio' +metaDescription: 'Learn how to install Prisma Studio, connect to your database, browse and edit records, and export to CSV or JSON. The visual database browser for Prisma projects.' --- ## Installation diff --git a/apps/docs/content/docs/studio/index.mdx b/apps/docs/content/docs/studio/index.mdx index 6e872da986..574bc23c69 100644 --- a/apps/docs/content/docs/studio/index.mdx +++ b/apps/docs/content/docs/studio/index.mdx @@ -3,7 +3,7 @@ title: Prisma Studio description: A visual database editor for viewing and managing your data with Prisma url: /studio metaTitle: Prisma Studio for Prisma Postgres -metaDescription: "Learn about the various ways of using Prisma Studio, from running locally, to using it in VS Code to embedding it in your own application." +metaDescription: 'Learn about the various ways of using Prisma Studio, from running locally, to using it in VS Code to embedding it in your own application.' --- [Prisma Studio](https://www.prisma.io/studio) works with or without Prisma ORM and supports the following workflows: diff --git a/apps/eclipse/content/design-system/components/accordion.mdx b/apps/eclipse/content/design-system/components/accordion.mdx index 6356c16ebc..d0853df51b 100644 --- a/apps/eclipse/content/design-system/components/accordion.mdx +++ b/apps/eclipse/content/design-system/components/accordion.mdx @@ -18,8 +18,7 @@ export function FAQSection() { return ( - Prisma is a next-generation ORM that makes working with databases easy for application - developers. + Prisma is a next-generation ORM that makes working with databases easy for application developers. You can get started by installing Prisma CLI and initializing a new project. @@ -38,8 +37,7 @@ export function FAQSection() { - Prisma is a next-generation ORM that makes working with databases easy for application - developers. + Prisma is a next-generation ORM that makes working with databases easy for application developers. You can get started by installing Prisma CLI and initializing a new project. @@ -59,9 +57,15 @@ import { Accordions, Accordion } from "@prisma/eclipse"; export function MultipleAccordion() { return ( - Run npm install to get started. - Configure your settings in the config file. - Import and use the components in your app. + + Run npm install to get started. + + + Configure your settings in the config file. + + + Import and use the components in your app. + ); } @@ -72,9 +76,15 @@ export function MultipleAccordion() { - Run npm install to get started. - Configure your settings in the config file. - Import and use the components in your app. + + Run npm install to get started. + + + Configure your settings in the config file. + + + Import and use the components in your app. + **With Hash Linking** @@ -87,10 +97,16 @@ import { Accordions, Accordion } from "@prisma/eclipse"; export function HashLinkedAccordion() { return ( - + Prisma is a next-generation ORM that makes working with databases easy. - + You can get started by installing Prisma CLI. @@ -105,10 +121,16 @@ When you provide an `id` prop, a copy link button appears that allows users to c - + Prisma is a next-generation ORM that makes working with databases easy. - + You can get started by installing Prisma CLI. diff --git a/apps/eclipse/content/design-system/components/alert.mdx b/apps/eclipse/content/design-system/components/alert.mdx index 24c1ebbaaf..e146742838 100644 --- a/apps/eclipse/content/design-system/components/alert.mdx +++ b/apps/eclipse/content/design-system/components/alert.mdx @@ -15,7 +15,11 @@ The Alert component displays important information with a default circle-exclama import { Alert } from "@prisma/eclipse"; export function BasicAlert() { - return This is a basic alert message with regular content.; + return ( + + This is a basic alert message with regular content. + + ); } ``` @@ -23,7 +27,9 @@ export function BasicAlert() {

Live Example:

-This is a basic alert message with regular content. + + This is a basic alert message with regular content. + **Alert Variants** @@ -35,13 +41,21 @@ import { Alert } from "@prisma/eclipse"; export function AlertVariants() { return ( <> - PPG variant - for general information and tips. + + PPG variant - for general information and tips. + - Success variant - for positive confirmations. + + Success variant - for positive confirmations. + - Warning variant - for important notices. + + Warning variant - for important notices. + - Error variant - for errors and critical information. + + Error variant - for errors and critical information. + ); } @@ -50,11 +64,17 @@ export function AlertVariants() {

Live Examples:

-PPG variant - for general information and tips. + + PPG variant - for general information and tips. + -Success variant - for positive confirmations. + + Success variant - for positive confirmations. + -Warning variant - for important notices. + + Warning variant - for important notices. + Error variant - for errors and critical information. @@ -75,10 +95,7 @@ export function CustomIconAlerts() { Alert with a custom terminal icon. - } - > + }> Alert with a custom error icon. @@ -86,10 +103,7 @@ export function CustomIconAlerts() { Alert with a custom success icon. - } - > + }> Alert with a custom warning icon. @@ -122,7 +136,6 @@ export function CustomIconAlerts() { Alert without an icon. -
**Rich MDX Content** @@ -135,9 +148,15 @@ import { Alert } from "@prisma/eclipse"; export function RichContentAlert() { return ( - **Getting Started** Follow these steps to get started: 1. Install the package: `npm install - @prisma/client` 2. Initialize Prisma: `npx prisma init` 3. Define your schema and run - migrations For more information, visit the [documentation](https://prisma.io/docs). + **Getting Started** + + Follow these steps to get started: + + 1. Install the package: `npm install @prisma/client` + 2. Initialize Prisma: `npx prisma init` + 3. Define your schema and run migrations + + For more information, visit the [documentation](https://prisma.io/docs). ); } @@ -148,9 +167,9 @@ export function RichContentAlert() { Follow these steps to get started: -1. Install the package: `npm install @prisma/client` -2. Initialize Prisma: `npx prisma init` -3. Define your schema and run migrations + 1. Install the package: `npm install @prisma/client` + 2. Initialize Prisma: `npx prisma init` + 3. Define your schema and run migrations For more information, visit the [documentation](https://prisma.io/docs). ::: @@ -197,36 +216,28 @@ export function ComplexAlerts() {

Live Examples:

- -
-

Important Update

-

- This feature will be deprecated in version 6.0. Please migrate to the new API: -

-
    -
  • - Old: prisma.user.findMany() -
  • -
  • - New: prisma.user.findMany({`{ where: {} }`}) -
  • -
-
-
+ +
+

Important Update

+

This feature will be deprecated in version 6.0. Please migrate to the new API:

+
    +
  • Old: prisma.user.findMany()
  • +
  • New: prisma.user.findMany({`{ where: {} }`})
  • +
+
+
- -
-

Connection Error

-

Unable to connect to the database. Please check:

-
    -
  • - Database credentials in .env -
  • -
  • Database server is running
  • -
  • Network connectivity
  • -
-
-
+ +
+

Connection Error

+

Unable to connect to the database. Please check:

+
    +
  • Database credentials in .env
  • +
  • Database server is running
  • +
  • Network connectivity
  • +
+
+
@@ -327,6 +338,8 @@ Maps to error variant. ::: ``` + +

Live Example:

@@ -350,4 +363,4 @@ You can include: - Icons are decorative and don't convey meaning alone - always include descriptive text - Color is not the only indicator of alert type (icons and context provide additional cues) - All variants maintain appropriate color contrast ratios -- Alert content is keyboard accessible and screen reader friendly +- Alert content is keyboard accessible and screen reader friendly \ No newline at end of file diff --git a/apps/eclipse/content/design-system/components/avatar.mdx b/apps/eclipse/content/design-system/components/avatar.mdx index 1ef6e539d1..84ff796bb7 100644 --- a/apps/eclipse/content/design-system/components/avatar.mdx +++ b/apps/eclipse/content/design-system/components/avatar.mdx @@ -20,18 +20,10 @@ export function UserProfile() { **Live Example:**
- - SM - - - MD - - - LG - - - XL - + SM + MD + LG + XL
**Avatar with Image** @@ -40,7 +32,14 @@ export function UserProfile() { import { Avatar } from "@prisma/eclipse"; export function UserProfileImage() { - return ; + return ( + + ); } ``` @@ -71,49 +70,13 @@ export function IconAvatar() {
- - - + - - - + - - - +
@@ -123,7 +86,15 @@ export function IconAvatar() { import { Avatar } from "@prisma/eclipse"; export function DisabledAvatar() { - return ; + return ( + + ); } ``` @@ -144,7 +115,12 @@ export function DisabledAvatar() { Display a user photo or image: ```tsx - + ``` #### Initials Avatar @@ -178,18 +154,10 @@ The Avatar component supports four sizes: ```tsx
- - SM - - - MD - - - LG - - - XL - + SM + MD + LG + XL
``` diff --git a/apps/eclipse/content/design-system/components/badge.mdx b/apps/eclipse/content/design-system/components/badge.mdx index 2ed9017b87..031c0e32d6 100644 --- a/apps/eclipse/content/design-system/components/badge.mdx +++ b/apps/eclipse/content/design-system/components/badge.mdx @@ -142,7 +142,6 @@ export function BadgeSizes() { ### Color Variants #### Neutral - Default badge style for general-purpose labels. ```tsx @@ -150,7 +149,6 @@ Default badge style for general-purpose labels. ``` #### PPG (Prisma Pulse & Accelerate) - Use for Prisma Pulse and Accelerate related features or content. ```tsx @@ -158,7 +156,6 @@ Use for Prisma Pulse and Accelerate related features or content. ``` #### ORM (Prisma ORM) - Use for Prisma ORM specific features or content. ```tsx @@ -166,7 +163,6 @@ Use for Prisma ORM specific features or content. ``` #### Success - Indicates successful states, active status, or positive information. ```tsx @@ -174,7 +170,6 @@ Indicates successful states, active status, or positive information. ``` #### Error - Indicates errors, deprecated features, or critical issues. ```tsx @@ -182,7 +177,6 @@ Indicates errors, deprecated features, or critical issues. ``` #### Warning - Indicates warnings, beta features, or items requiring attention. ```tsx @@ -215,7 +209,6 @@ Indicates warnings, beta features, or items requiring attention. ### Common Use Cases **API Status** - ```tsx @@ -223,7 +216,6 @@ Indicates warnings, beta features, or items requiring attention. ``` **Feature Availability** - ```tsx @@ -231,7 +223,6 @@ Indicates warnings, beta features, or items requiring attention. ``` **Content Types** - ```tsx @@ -239,7 +230,6 @@ Indicates warnings, beta features, or items requiring attention. ``` **Version Status** - ```tsx @@ -251,4 +241,4 @@ Indicates warnings, beta features, or items requiring attention. - Badge text has sufficient color contrast for readability - Badges are semantic elements that convey meaning through both color and text - Text labels ensure the meaning is clear even if color cannot be perceived -- Badges use Eclipse design tokens for consistent, accessible styling +- Badges use Eclipse design tokens for consistent, accessible styling \ No newline at end of file diff --git a/apps/eclipse/content/design-system/components/banner.mdx b/apps/eclipse/content/design-system/components/banner.mdx index df985496ba..0af0561c2d 100644 --- a/apps/eclipse/content/design-system/components/banner.mdx +++ b/apps/eclipse/content/design-system/components/banner.mdx @@ -23,18 +23,19 @@ export function BasicBanner() { import { Banner } from "@prisma/eclipse"; export function BannerWithIcon() { - return ; + return ( + + ); } ``` **Live Example:**
- +
**Color Variants** diff --git a/apps/eclipse/content/design-system/components/breadcrumb.mdx b/apps/eclipse/content/design-system/components/breadcrumb.mdx index 70443e44c2..c59ee2a95d 100644 --- a/apps/eclipse/content/design-system/components/breadcrumb.mdx +++ b/apps/eclipse/content/design-system/components/breadcrumb.mdx @@ -228,7 +228,6 @@ export function NextLinkBreadcrumb() { The root container for the breadcrumb navigation. **Props:** - - Extends all standard HTML `
); diff --git a/apps/eclipse/content/design-system/components/spinner.mdx b/apps/eclipse/content/design-system/components/spinner.mdx index 6555c06aa6..e8dfb35c62 100644 --- a/apps/eclipse/content/design-system/components/spinner.mdx +++ b/apps/eclipse/content/design-system/components/spinner.mdx @@ -83,7 +83,9 @@ export function ErrorSpinnerWithMessage() { return (
- Failed to load. Retrying... + + Failed to load. Retrying... +
); } @@ -93,7 +95,9 @@ export function ErrorSpinnerWithMessage() {
- Failed to load. Retrying... + + Failed to load. Retrying... +
**In Button** @@ -154,7 +158,9 @@ export function SpinnerWithText() { return (
- Loading your data... + + Loading your data... +
); } @@ -164,7 +170,9 @@ export function SpinnerWithText() {
- Loading your data... + + Loading your data... +
**Full Page Loading** @@ -177,7 +185,9 @@ export function FullPageLoader() {
-

Loading application...

+

+ Loading application... +

); @@ -232,7 +242,7 @@ export function AsyncButton() { const handleClick = async () => { setLoading(true); try { - await fetch("/api/data"); + await fetch('/api/data'); } finally { setLoading(false); } @@ -241,7 +251,7 @@ export function AsyncButton() { return ( ); } @@ -282,19 +292,19 @@ import { Button } from "@prisma/eclipse"; import { useState } from "react"; export function DataFetcherWithRetry() { - const [status, setStatus] = useState<"idle" | "loading" | "error" | "success">("idle"); + const [status, setStatus] = useState<'idle' | 'loading' | 'error' | 'success'>('idle'); const handleFetch = async () => { - setStatus("loading"); + setStatus('loading'); try { - await fetch("/api/data"); - setStatus("success"); + await fetch('/api/data'); + setStatus('success'); } catch (error) { - setStatus("error"); + setStatus('error'); } }; - if (status === "loading") { + if (status === 'loading') { return (
@@ -303,14 +313,12 @@ export function DataFetcherWithRetry() { ); } - if (status === "error") { + if (status === 'error') { return (
Failed to load. - +
); } @@ -350,7 +358,9 @@ export function LoadingCard() {
-

Loading content...

+

+ Loading content... +

@@ -433,7 +443,7 @@ import { useQuery } from "@tanstack/react-query"; export function DataFetcher() { const { data, isLoading, error } = useQuery({ - queryKey: ["data"], + queryKey: ['data'], queryFn: fetchData, }); @@ -499,7 +509,7 @@ export function ContactForm() { Sending... ) : ( - "Send Message" + 'Send Message' )} diff --git a/apps/eclipse/content/design-system/components/statistic.mdx b/apps/eclipse/content/design-system/components/statistic.mdx index 48b82b0842..938a4506d2 100644 --- a/apps/eclipse/content/design-system/components/statistic.mdx +++ b/apps/eclipse/content/design-system/components/statistic.mdx @@ -46,7 +46,13 @@ import { Statistic } from "@prisma/eclipse"; export function StatWithBadge() { return ( - + ); } ``` @@ -54,7 +60,13 @@ export function StatWithBadge() { **Live Example:**
- +
**Multiple Statistics Grid** @@ -66,13 +78,7 @@ export function StatsGrid() { return (
- +
); @@ -98,13 +104,7 @@ export function ColoredBadges() { - +
); } @@ -155,35 +155,30 @@ export function ColoredBadges() { ### Common Use Cases **Performance Metrics** - ```tsx ``` **Database Statistics** - ```tsx ``` **Usage Analytics** - ```tsx ``` **Health Monitoring** - ```tsx ``` **Resource Usage** - ```tsx @@ -192,7 +187,6 @@ export function ColoredBadges() { ### Typography & Spacing The Statistic component uses: - - **Title**: Uppercase, extra-small font (text-xs), gray-400 color - **Value**: Extra-large, bold font (text-4xl font-black) - **Measure**: Large font (text-lg), gray-500 color @@ -202,7 +196,6 @@ The Statistic component uses: ### Layout Tips **Single Column (Mobile)** - ```tsx
@@ -211,7 +204,6 @@ The Statistic component uses: ``` **Two Column Grid** - ```tsx
@@ -222,7 +214,6 @@ The Statistic component uses: ``` **Three Column Grid** - ```tsx
@@ -232,7 +223,6 @@ The Statistic component uses: ``` **Four Column Grid** - ```tsx
@@ -254,7 +244,6 @@ The Statistic component uses: ### Design Tokens The component uses Eclipse design tokens: - - Border: `border-purple-400` with `border-2 border-dashed` - Radius: `rounded-lg` - Title: `text-xs font-semibold tracking-widest text-gray-400 uppercase` diff --git a/apps/eclipse/content/design-system/components/steps.mdx b/apps/eclipse/content/design-system/components/steps.mdx index 0b6e858969..d62507b1dd 100644 --- a/apps/eclipse/content/design-system/components/steps.mdx +++ b/apps/eclipse/content/design-system/components/steps.mdx @@ -17,9 +17,15 @@ import { Steps, Step } from "@prisma/eclipse"; export function GettingStarted() { return ( - ### Install dependencies - ### Configure environment - ### Start the server + + ### Install dependencies + + + ### Configure environment + + + ### Start the server + ); } @@ -28,9 +34,15 @@ export function GettingStarted() { ### Live example - #### Install dependencies - #### Configure environment - #### Start the server + + #### Install dependencies + + + #### Configure environment + + + #### Start the server + ### With Code Blocks @@ -39,15 +51,28 @@ Steps can contain code blocks and other rich content: - #### Create a new project Initialize a new Next.js project with TypeScript: ```bash npx - create-next-app@latest my-app --typescript ``` + #### Create a new project + + Initialize a new Next.js project with TypeScript: + + ```bash + npx create-next-app@latest my-app --typescript + ``` - #### Install Prisma Add Prisma to your project: ```bash npm install prisma --save-dev npx prisma - init ``` + #### Install Prisma + + Add Prisma to your project: + + ```bash + npm install prisma --save-dev + npx prisma init + ``` - #### Configure database Update your `schema.prisma` file with your database configuration. + #### Configure database + + Update your `schema.prisma` file with your database configuration. @@ -57,15 +82,30 @@ Perfect for onboarding flows and tutorials: - #### Welcome to Prisma Prisma is a next-generation ORM that makes database access easy and - type-safe. + #### Welcome to Prisma + + Prisma is a next-generation ORM that makes database access easy and type-safe. - #### Connect your database Configure your database connection in the `.env` file. - #### Define your schema Create your data models in `schema.prisma`. - #### Generate Prisma Client Run `npx prisma generate` to create your type-safe database client. + #### Connect your database + + Configure your database connection in the `.env` file. + + + #### Define your schema + + Create your data models in `schema.prisma`. + + + #### Generate Prisma Client + + Run `npx prisma generate` to create your type-safe database client. + + + #### Start building + + You're ready to start building with Prisma! - #### Start building You're ready to start building with Prisma! ### With Checked Steps @@ -79,13 +119,21 @@ export function CompletedSteps() { return ( - #### Create account Account created successfully. + #### Create account + Account created successfully. + + + #### Verify email + Email verified. - #### Verify email Email verified. - #### Complete profile Currently filling out profile information. + #### Complete profile + Currently filling out profile information. + + + #### Start using the app + Ready to begin after profile completion. - #### Start using the app Ready to begin after profile completion. ); } @@ -97,13 +145,21 @@ export function CompletedSteps() { - #### Create account Account created successfully. + #### Create account + Account created successfully. + + + #### Verify email + Email verified. - #### Verify email Email verified. - #### Complete profile Currently filling out profile information. + #### Complete profile + Currently filling out profile information. + + + #### Start using the app + Ready to begin after profile completion. - #### Start using the app Ready to begin after profile completion. ### With Active Steps @@ -116,10 +172,22 @@ import { Steps, Step } from "@prisma/eclipse"; export function ProgressIndicator() { return ( - #### Create account Your account has been created successfully. - #### Verify email Email verification in progress... - #### Complete profile Add your personal information (upcoming). - #### Start using the app Begin exploring features (upcoming). + + #### Create account + Your account has been created successfully. + + + #### Verify email + Email verification in progress... + + + #### Complete profile + Add your personal information (upcoming). + + + #### Start using the app + Begin exploring features (upcoming). + ); } @@ -130,10 +198,26 @@ export function ProgressIndicator() {
- #### Create account Sign up for a new account with your email. - #### Verify email Check your inbox and click the verification link. - #### Complete profile Add your personal information and preferences. - #### Start using the app You're all set! Begin exploring features. + + #### Create account + + Sign up for a new account with your email. + + + #### Verify email + + Check your inbox and click the verification link. + + + #### Complete profile + + Add your personal information and preferences. + + + #### Start using the app + + You're all set! Begin exploring features. + ### Vertical Variant @@ -146,9 +230,15 @@ import { Steps, Step } from "@prisma/eclipse"; export function VerticalSteps() { return ( - #### Install dependencies - #### Configure environment - #### Start the server + + #### Install dependencies + + + #### Configure environment + + + #### Start the server + ); } @@ -159,8 +249,12 @@ export function VerticalSteps() {
- #### Install dependencies - #### Configure environment + + #### Install dependencies + + + #### Configure environment + #### Start the server @@ -181,18 +275,15 @@ export function VerticalSteps() { ### Prop Details The `variant` prop controls the layout direction: - - **`"horizontal"`** (default) - Horizontal progress indicator layout with separators between steps - **`"vertical"`** - Traditional vertical timeline layout with connector lines The `checked` prop replaces the step number with a checkmark (✓) icon to indicate completion. This is useful for: - - **Progress tracking** - Show which steps have been completed - **Onboarding flows** - Guide users through setup with visual feedback - **Form wizards** - Indicate completed sections The `active` prop visually highlights the step indicator and content to show that it's the current step, a completed step, or an important step. Steps without this prop appear dimmed to indicate they are future, optional, or not yet completed. This is useful for: - - **Progress indicators** - Show which steps are active/completed vs. upcoming - **Conditional workflows** - Highlight required steps while dimming optional ones - **Multi-stage processes** - Indicate current and completed stages in a process diff --git a/apps/eclipse/content/design-system/components/switch.mdx b/apps/eclipse/content/design-system/components/switch.mdx index eb898e67b4..0ec92903bf 100644 --- a/apps/eclipse/content/design-system/components/switch.mdx +++ b/apps/eclipse/content/design-system/components/switch.mdx @@ -4,10 +4,7 @@ description: A toggle switch component built on Radix UI for binary on/off state --- import { Switch, Field, FieldLabel, FieldDescription } from "@prisma/eclipse"; -import { - ControlledSwitchExample, - SwitchFormExample, -} from "@/components/switch-examples/interactive-examples"; +import { ControlledSwitchExample, SwitchFormExample } from "@/components/switch-examples/interactive-examples"; ### Usage @@ -38,7 +35,10 @@ export function SwitchWithLabel() { return (
-
@@ -50,8 +50,8 @@ export function SwitchWithLabel() {
-
+ + **Disabled State** ```tsx @@ -131,13 +139,19 @@ export function DisabledSwitch() {
-
-
@@ -151,8 +165,8 @@ export function DisabledSwitch() {
-
-