File: README.md
Deprecation notice
the source code of the NHost documentation moved to the main nhost/nhost repository.
File: jsconfig.json
{
"compilerOptions": {
"baseUrl": "./src"
}
}
File: package.json
{
"name": "docs",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"export": "next export",
"start": "next start"
},
"dependencies": {
"@mdx-js/loader": "^1.6.22",
"@mdx-js/react": "^1.6.22",
"@next/mdx": "^9.4.4",
"autoprefixer": "^10.2.4",
"classnames": "^2.2.6",
"mdx-prism": "https://github.com/doamatto/mdx-prism",
"next": "^11.1.0",
"next-mdx-enhanced": "^5.0.0",
"next-seo": "^4.7.3",
"next-themes": "^0.0.14",
"postcss": "^8.2.10",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-medium-image-zoom": "^4.3.1",
"rehype": "^11.0.0",
"remark-code-titles": "^0.1.1",
"remark-unwrap-images": "^2.0.0",
"smartlook-client": "^4.6.1"
},
"devDependencies": {
"postcss-preset-env": "^6.7.0",
"tailwindcss": "^2.0.3",
"webpack": "^5.28.0",
"webpack-cli": "^4.5.0"
}
}
File: src/pages/index.mdx
title: "Welcome to Nhost" publishedAt: "2020-07-29"
Nhost is a full-fledged serverless backend for Jamstack and client-serverless applications. It enables developers to build dynamic websites without having to worry about infrastructure, data storage, data access and user management.
Nhost was inspired by Google Firebase, but uses SQL, GraphQL and has no vendor lock-in.
Get started
If you are new to Nhost we recommend the quick start guide. You will learn the basic concepts with Nhost and build a Todo-app with react.
Discover Nhost
Every Nhost project is a production ready backend:
PostgreSQL
PostgreSQL is the world's most advanced open-source relational database. Your data will be instantly accessible over GraphQL and any modification to your database instance can be done through the Hasura console.
Hasura
Hasura provides access to your data layer through an instant realtime GraphQL API based on your PostgreSQL database. Managing your data, schemas, permissions, event triggers, remote schemas, etc is done through the Hasura console.
GraphQL
A realtime GraphQL API with full CRUD capabilities is instantly available.
Authentication
Register, login and manage users. Either with email and password or via external OAuth providers such as GitHub, Google, Facebook, etc. All users are stored in a users table.
Storage
Let users upload and download files. Control what users are allowed to read/write using storage security rules. All files are stored in a MinIO S3 server which you also have direct access to.
Open Source
All software in a Nhost project is open-source. This means no vendor lock-in.
GitHub integration
Connect your GitHub repository to your Nhost project. Push changes for automatic deployment to production.
File: src/pages/auth/api-reference.mdx
title: "API Reference for Nhost Authentication"
User
| Endpoint | Method | Description |
|---|---|---|
| /auth/register | POST | Register user |
| /auth/login | POST | Login user |
| /auth/logout | POST | Logout user |
| /auth/activate | POST | Activate user |
| /auth/token/refresh | GET | Get new refresh token |
| /auth/token/revoke | POST | Revoke refresh token |
| /auth/delete | POST | Delete user |
Password
| Endpoint | Method | Description |
|---|---|---|
| /auth/change-password/ | POST | Change password as logged in |
| /auth/change-password/request | POST | Request to change password |
| /auth/change-password/change | POST | Change password with ticket |
| Endpoint | Method | Description |
|---|---|---|
| /auth/change-email/ | POST | Change email as logged in |
| /auth/change-email/request | POST | Request to change email |
| /auth/change-email/change | POST | Change email with ticket |
MFA (Multi-Factor Authentication)
| Endpoint | Method | Description |
|---|---|---|
| /auth/mfa/generate | POST | Generate MFA QR-code |
| /auth/mfa/enable | POST | Enable MFA |
| /auth/mfa/disable | POST | Disable MFA |
| /auth/mfa/totp | POST | TOTP login |
Register user
Register a new user.
nhost-js-sdk
auth.register({ email, password });With custom user_data, for ex display_name:
auth.register({
email,
password,
options: {
userData: {
display_name: "Joe Doe",
},
},
});Request
POST /auth/register
{
"email": "[email protected]",
"password": "password"
}With custom user_data, for ex display_name:
{
"email": "[email protected]",
"password": "password",
"user_data": {
"display_name": "Elon Musk"
}
}Response
{
"session": {
"jwt_token": "e3MmYZSJdLCJ4LWhhjE0MDg3Nzc3fQ.d4kdsiBjD3MpPpbNNbaMt-gyHHVXwrSvrAtVcEQ_jB8",
"jwt_expires_in": 900000,
"user": {
"id": "kcf72f45-5a2d-4615-810d-96e10548bb35",
"display_name": "[email protected]",
"email": "[email protected]"
},
"refresh_token": "dd69aafd-71f6-4f97-be93-9bdbfb192fe6"
},
"user": {
"id": "kcf72f45-5a2d-4615-810d-96e10548bb35",
"display_name": "[email protected]",
"email": "[email protected]"
}
}If
AUTO_ACTIVATE_NEW_USERSisfalse,sessionwill returnnull.
Login user
Login user
nhost-js-sdk
auth.login({ email, password });Request
POST /auth/login
{
"email": "[email protected]",
"password": "password"
}Response
Set-Cookie: refresh_token=...
Set-Cookie: permission_variables=...{
"session": {
"jwt_token": "e3MmYZSJdLCJ4LWhhjE0MDg3Nzc3fQ.d4kdsiBjD3MpPpbNNbaMt-gyHHVXwrSvrAtVcEQ_jB8",
"jwt_expires_in": 900000,
"user": {
"id": "kcf72f45-5a2d-4615-810d-96e10548bb35",
"display_name": "[email protected]",
"email": "[email protected]"
},
"refresh_token": "dd69aafd-71f6-4f97-be93-9bdbfb192fe6"
},
"user": {
"id": "kcf72f45-5a2d-4615-810d-96e10548bb35",
"display_name": "[email protected]",
"email": "[email protected]"
}
}If Multi-Factor Authentication (MFA) is enabled for the account, the following response body is returned:
{
"session": null,
"user": null,
"mfa": {
"ticket": "762ea295-4a12-436f-b8fc-36b91aefb28e"
}
}For login with MFA, proceed authentication by requesting the TOTP /auth/mfa/totp endpoint.
Logout user
Logout user.
nhost-js-sdk
auth.logout(all: boolean = false);Request
POST /auth/login?all=
if all = true all of the user's refresh token will be revoked from the database, effectevly logging out the user from all devices. If all = false only the current refresh token will be deleted and the user will only logout from the current device. all is optional and defaults to false.
Response
{
"session": null,
"user": null
}Activate user
Activate user.
This is only needed if users is not automatically activated. If users are not automatically activated Nhost will send an email, containing the activation URL, to the user to activate their account. The email template for account activate can be edited.
nhost-js-sdk
auth.activate(ticket: string);Request
POST /auth/activate?ticket=<ticket>
Response
204 No ContentGet refresh token
When a user login Nhost will set a refresh-token as a cookie. That refresh-token is used to request a new JWT-token, since JWT-tokens are short lived (15 min). Refresh-tokens are long lived (1 year).
The refresh-token cookie is a Secure HttpOnly cookie and can not be accessed by the browser because only Nhost should be able to read the cookie.
nhost-js-sdk
nhost-js-sdk automatically handle refreshing-tokens for logged in users in the background. No action required.
Request
GET /auth/token/refresh
Cookie: refresh_token=<current_refresh_token>Response
Cookie: refresh_token=<new_refresh_token>{
"session": {
"jwt_token": "e3MmYZSJdLCJ4LWhhjE0MDg3Nzc3fQ.d4kdsiBjD3MpPpbNNbaMt-gyHHVXwrSvrAtVcEQ_jB8",
"jwt_expires_in": 900000,
"user": {
"id": "kcf72f45-5a2d-4615-810d-96e10548bb35",
"display_name": "[email protected]",
"email": "[email protected]"
},
"refresh_token": "dd69aafd-71f6-4f97-be93-9bdbfb192fe6"
},
"user": {
"id": "kcf72f45-5a2d-4615-810d-96e10548bb35",
"display_name": "[email protected]",
"email": "[email protected]"
}
}Revoke refresh-token
Revoke a refresh-token.
nhost-js-sdk
not implemented
Request
GET /auth/token/refresh
Cookie: refresh_token=<current_refresh_token>Response
204 No ContentDelete user
Used so user can delete themselves. This will delete the user in the users table which will cascade and delete all associated data for the particular user if ON DELETE CASCADE is set for Foreign keys.
This endpoint is only active if you allow users to delete themselves in Settings -> Authentication. (Not implemented yet, default value is Not active)
nhost-js-sdk
not implemented
Request
POST /auth/delete
Set-Cookie: refresh_token=...
Set-Cookie: permission_variables=...Response
204 No ContentChange password
For users to change their password when they are logged in and remember their previous password.
nhost-js-sdk
auth.changePassword(oldPassword: string, newPassword: string);Request
POST /auth/change-password
Set-Cookie: refresh_token=...
Set-Cookie: permission_variables=...{
"old_password": "<old password>",
"new_password": "<new password>"
}Response
204 No ContentRequest to change password
If a user has forget their password they can request to change their password.
An email will be sent out to the user with a ticket that should be used to update the user's password. You can change the password reset email template.
nhost-js-sdk
auth.requestPasswordChange(email: string);Request
POST /auth/change-password/request
{
"email": "<user's email>"
}Response
204 No ContentThis endpoint will always return HTTP status code 204 in order to not leak information about the database.
Change password with ticket
Change password with ticket from the email.
nhost-js-sdk
auth.confirmPasswordChange(newPassword: string, ticket: string);Request
POST /auth/change-password/change
{
"new_password": "<new password>",
"ticket": "<ticket>"
}Response
204 No ContentGenerate MFA QR-code
Generate MFA (Multi-Factor Authentication) QR-code. The user must be logged in to generate this QR-code. The user should scan the QR-code with their password manager. The password manager will return a code (one-time password) that will be used to enable and disable MFA for the user and login the user using TOTP login.
nhost-js-sdk
auth.MFAGenerate(code: string, ticket: string);Request
POST /auth/mfa/generate
Response
{
"image_url": "<base64 data image of the qe code>",
"otp_secret": "<otp secret>"
}Enable MFA
Enable MFA (Multi-Factor Authentication). Use the code (one-time password) in the user's password manager
nhost-js-sdk
auth.MFAEnable(code: string);Request
POST /auth/mfa/enable
{
"code": "123456"
}Response
204 No ContentDisable MFA
Disable MFA (Multi-Factor Authentication). Use the code (one-time password) in the user's password manager
nhost-js-sdk
Not implemented yet
Request
POST /auth/mfa/disable
{
"code": "code from mfa client"
}Response
204 No ContentTOTP login
Time-based One-Time Password login. Use the ticket from Login response body and the code from the user's password manager.
nhost-js-sdk
auth.MFATotp(code: string, ticket: string);Request
POST /auth/mfa/generate
Response
{
"image_url": "<base64 data image of the qe code>",
"otp_secret": "<otp secret>"
}File: src/pages/auth/custom-user-columns.mdx
title: "Custom user columns"
You can add any custom columns to the users table.
Don't remove any predefined user columns.
JWT claims
All custom columns on the users table can be added to the JWT-token as claims. These claims can then be used to set Hasura permissions.
As an example, let's say you have a company table and a company_id column to the users table. With that, you can now add company_id as an x-hasura-company-id key to your JWT claims.
The new JWT-token contains the company_id as x-hasura-company-id.
{
"https://hasura.io/jwt/claims": {
"x-hasura-user-id": "f1126a1e-49e8-47b9-b9cc-e6dd5d568c7e",
"x-hasura-allowed-roles": ["user", "me"],
"x-hasura-default-role": "user",
"x-hasura-company-id": "b3b70430-49db-4c06-b365-efb498e59913"
},
"iat": 1595247544,
"exp": 1595247844
}x-hasura-company-id can now be used as a permission variable for Hasura permissions.
In the example below, a user is allowed to select information about their own company (id = x-hasura-company-id).
Set user field data on registration
You can specify what user fields that can be set during registration under Settings -> Authentication -> REGISTRATION CUSTOM FIELDS.
A popular field to let users set during registration is display_name that exist on the users table by default.
With nhost-js-sdk you can register a user, with the display_name like this:
import { auth } from "utils/nhost";
const email = "[email protected]";
const password = "123123";
const displayName = "Elon Musk";
auth.register({
email,
password,
options: {
userData: {
display_name: displayName,
},
},
});File: src/pages/auth/email-templates.mdx
title: "Email templates"
Activate Account Email Template
This email is sent to new users to activate their account. Use the url and ticket variables ([Activate your account]({{ url }}/auth/activate?ticket={{ ticket }})) to let users activate their account.
This email is sent only if the setting User registration auto active is off and Require to verify new emails when user change email is on.
Change those settings under Settings -> Auth in the Nhost console.
Variables
display_name- user's display name.ticket- UUID token to activate the user's account.url- Backend server URL, usuallyhttps://backend-xxx.nhost.app.
Password Reset Email Template
Email to users requesting to change their password, usually because the user has forgot their current password.
The URL in this email should go to your app with a form to change the user's password. Use the ticket to authenticate the password change request.
Variables
ticket- UUID token to change password.
Change Email Email Template
Email for users requesting to change their current email. This email is sent to the new email address the user wants to change to, in order to confirm the new email address.
The URL in this email should go to your app. Your app should receive the ticket and change the email.
Variables
ticket- UUID token used to change email.
How To Use Variables in Email Templates
Use variables in email-templates with curly brackets.
<p>Hi {{ display_name }}</p>
<p>
<a href="{{ url }}/auth/activate?ticket={{ ticket }}">
Activate your account
</a>
</p>SMTP Settings
By default, all emails are sent with Nhost's SMTP settings from nhost.app using Sendgrid. You can change the SMTP settings to your own at Settings -> SMTP.
More Settings
Verify emails
Send emails to verify (activate) a new account and verify a new email a user whish to change to.
If Verify emails is disabled, Nhost won't send an activation email to new users nor send emails when a user whish to change their email. For user activation, you need to handle that your self (if you don't automatically activate new users). For email change, the email will be changed instantly with out verification.
HIBP
If activated, passwords will be checked against HIBP (haveibeenpwned.com). Weak and compromised passwords will not be accepted.
File: src/pages/auth/index.mdx
title: "Nhost Authentication"
Your app probably needs a way to authenticate users. That's easy with Nhost.
Nhost Authentication integrates tightly with other services in your Nhost project, such as Hasura and Storage.
You can log in users with:
- Email / Password
- Github
For easy integration with auth (and storage) in your app, use nhost-js-sdk.
Tokens
Nhost Authentication uses JWT-tokens and refresh-tokens. A JWT token is used in a GraphQL API request to authenticate the user and to resolve Hasura permissions. A refresh tokens is used to request new JWT-token.
A JWT-token can not be revoked; they can only expire. Because of that, JWT tokens are short-lived (15 min). Before a JWT-token expires, the client should request a new JWT-token using the refresh token. A refresh token can be revoked and is long-lived (1 year). To revoke a refresh-token, remove the refresh-token from the database in the auth.refresh_tokens table.
You can configure the expiration time for both JWT-tokens and refresh-tokens in the Nhost console under Settings -> Authentication.
By default, each JWT-token includes the user's id and roles as claims. Here's an example:
{
"https://hasura.io/jwt/claims": {
"x-hasura-allowed-roles": ["user", "me"],
"x-hasura-default-role": "user",
"x-hasura-user-id": "c8ee8353-b886-4530-9089-631ea7fd4c8a"
},
"iat": 1595146465,
"exp": 1595147365
}You can add more claims to the JWT token using custom user columns.
File: src/pages/auth/local-users.mdx
title: "Local users"
The easiest way for users to signup is using email and password. All users are stored in the users table, and relevant account information is saved in the tables under the auth schema.
Passwords are hashed using bcrypt with multiple salt rounds.
Quick Start
The easiest way to register and login users is with nhost-js-sdk.
Install
npm install --save nhost-js-sdk
Setup
In ex /src/utils/nhost.js:
import { createClient } from "nhost-js-sdk";
const config = {
baseURL: "https://backend-REPLACE.nhost.app",
};
const { auth, storage } = createClient(config);
export { auth, storage };Usage
Register a user:
import { auth } from "utils/nhost";
const email = "[email protected]";
const password = "123123";
auth.register({ email, password });Login user:
import { auth } from "utils/nhost";
const email = "[email protected]";
const password = "123123";
auth.login({ email, password });Learn more about the nhost-js-sdk.
Learn more about the Auth API Reference.
Manage users
All users are manageable in the Nhost console in the Auth tab.
File: src/pages/auth/multi-factor-authentication.mdx
title: "Multi-Factor Authentication (MFA)"
Multi-Factor Authentication (MFA), also called Two-Factor Authentication (2FA) improves login security for users.
Nhost uses Time-based One-time Password algorithm (TOTP) for MFA.
If MFA is enabled, a user must both use email/password and a Time-based One-Time Password (TOPT) to be able to login.
By default, users has MFA disabled.
How to enable for a user:
- Generate MFA QR code
- Scan MFA QR code with password manager
- Get a
code(one-time password) from password manager - Use
codeto enable MFA.
How to disable MFA for a user
- Get a
code(one-time password) from password manager - Use
codeto disable MFA.
How to login when MFA is enabled
- Login using email and password
- Save
ticketresponse from the login - Copy
codeone-time password from password manager - Send
ticketandcodeto TOTP login - User is logged in.
MFA clients
If you have a password manager there is usually built-in support for MFA.
You can also use a stand-alone MFA client such as Google Authenticator.
File: src/pages/auth/roles.mdx
title: "Roles"
Each user can have one or more roles. By default, each user has the roles user and me.
A role is used to set Hasura Permissions and Storage Security Rules.
A user's roles are defined in the JWT-token that is automatically generated when a user logs in.
Here's an example:
{
"https://hasura.io/jwt/claims": {
"x-hasura-allowed-roles": ["user", "me"],
"x-hasura-default-role": "user",
"x-hasura-user-id": "c8ee8353-b886-4530-9089-631ea7fd4c8a"
},
"iat": 1595146465,
"exp": 1595147365
}Default role
The x-hasura-default-role claim is the default role of the user. This role is used for every request unless you change the role manually during the request (See Change role in a GraphQL request). By default the default role is user.
Allowed roles
A user can have multiple allowed roles. Allowed roles are roles that the user can use to make a GraphQL request. A GraphQL request can only use one role per request.
Me role
The me role can be used to set user specific permission for the own user. For example, you might want to allow all users (user role) to select id and display_name for all users in your app. But each user should also be able to see their own birthday. You can use the me role to select the birthday.
Change role in a GraphQL request
A user can override the default role in a GraphQL API request by setting the x-hasura-role header. The new role must be in the x-hasura-allowed-roles array.
As an example, let's say a user has the roles user, me and editor:
x-hasura-default-role: user
x-hasura-allowed-roles: ["user", "me", "editor"]Then the user can make a request as an editor like this:
const { loading, error, data } = useQuery(MY_QUERY, {
context: {
headers: {
"x-hasura-role": "editor",
},
},
});Default allowed user roles
A user can have multiple default roles directly on registration. The default allowed user roles can be set in Settings -> Authentication -> DEFAULT ALLOWED ROLES.
Public role
If a user is not logged-in and no header or token is sent with the GraphQL request, Hasura resolves the request using the public role.
HASURA_GRAPHQL_UNAUTHORIZED_ROLE is set to
publicby default for all Nhost projects.
File: src/pages/auth/oauth-providers/facebook.mdx
title: "Facebook OAuth"
Here is a guide on how to set up OAuth with Facebook:
https://developers.facebook.com/docs/facebook-login/
File: src/pages/auth/oauth-providers/github.mdx
title: "Github OAuth"
To enable Github as a Auth provider you must first create an OAuth App in Github. On Github, go to Settings > Developer settings > OAuth apps and click New OAuth App.
Set a name, a homepage URL and the Authorization callback URL. The Authorization callback URL you should use is inside the Nhost console under Settings > Sign-methods > Github (see last image).
The Github OAuth app is now created. Finally create a Client Secret. Copy both Client ID and Client Secret and insert them in the Nhost console and click Save.
Your app is now configured with Github and users can now register and sign in with Github.
To register users, simply direct them to https://backend-REPLACE.nhost.app/auth/providers/github.
Example:
<a href="https://backend-REPLACE.nhost.app/auth/providers/github">
Sign in with Github
</a>File: src/pages/auth/oauth-providers/google.mdx
title: "Google OAuth"
Here is a guide on how to set up OAuth with Google:
https://support.google.com/cloud/answer/6158849
File: src/pages/auth/oauth-providers/index.mdx
title: "OAuth overview"
With Nhost, it's easy to sign in users with external OAuth providers. OAuth providers are configured in the Nhost console at Settings -> Sign-in methods.
Supported OAuth providers
- Github
Redirect URLs
You must first set the auth redirect URLs to start using an OAuth provider. The redirect URL specifies where your users should return to once the signup/signin is complete.
If you develop locally, set the Redirect URLs to your local web app. Ex http://localhost:3000.
How to sign up and sign in users
The flow for both signup and signin is the same. Link to https://backend-REPLACE.nhost.app/auth/providers/[provider] in your signup/signin page.
[provider]can be one ofgithub,
File: src/pages/auth/oauth-providers/linkedin.mdx
title: "LinkedIn OAuth"
Here is a guide on how to set up OAuth with LinkedIn:
https://www.linkedin.com/developers/apps/new
Note that you need to add Sign In with LinkedIn as a product under Products.
File: src/pages/cli/deploy.mdx
title: "Nhost Deploy"
Deploy pushes your local changes to your production Nhost project
$ nhost deploy
✔ migrations deployed
✔ metadata deployedAll the local changes should now be reflected in production.
Deploy using Git
If the repository is connected to the Nhost project deployment will happen automatically on push to the master or the main branch.
File: src/pages/cli/dev.mdx
title: "Nhost Dev"
Nhost Dev
nhost dev starts your local development environment.
$ nhost dev
⠇ Nhost is starting...
# The first time dev is run takes a bit longer because the database is being created
✔ Local Nhost backend is running!
GraphQL API: http://localhost:8080/v1/graphql
Hasura Console: http://localhost:9695
Auth & Storage: http://localhost:9000
API: http://localhost:4000The local websocket url is
ws://localhost:8080/v1/graphql
Great! You now have a Nhost backend locally with all services:
- PostgreSQL
- Hasura GraphQL Engine
- Hasura Backend Plus
- Custom API
The Hasura console (http://localhost:9695) was launched. Use this URL specifically to make changes in Hasura to track migrations and metadata automatically (recommended). All metadata and migrations are stored in the nhost/ folder.
Logs
Get local logs for each service when you develop locally.
PostgreSQL
$ docker logs -f nhost_postgresHasura GraphQL Engine
$ docker logs -f nhost_hasuraHasura Backend Plus
$ docker logs -f nhost_hbpCustom API
$ docker logs -f nhost_apiConfiguration
Note that for the below configurations to take effect, you need to restart
nhost dev.
Permission variables
To add permission variables to the JWT token set JWT_CUSTOM_FIELDS in .env.development.
Example: if every user has a company_id column that should be used as a permission variable in the JWT token (x-hasura-company-id):
JWT_CUSTOM_FIELDS=company_idRegistration custom fields
To add registration custom fields set REGISTRATION_CUSTOM_FIELDS in .env.development
Example: If users should be able to set display_name on registration.
REGISTRATION_CUSTOM_FIELDS=display_nameNetworking
The docker containers all expose their ports directly on the host machine, so you can both reach them on localhost as well as on the host machine IP address from another machine connected to the same network (if firewall rules allows).
Expo
In Expo, to make development use the dev environment both in the simulator and in Expo Go and production use production, see code below:
export const URL =
Constants.manifest.packagerOpts.dev && !Constants.isDevice
? "http://localhost:9001"
: typeof manifest.packagerOpts === `object` && manifest.packagerOpts.dev
? `http://${manifest.debuggerHost.split(`:`).shift().concat(`:9001`)}`
: "nhost.io cloud url";File: src/pages/cli/down.mdx
title: "Nhost down"
nhost down stops and removes the local Nhost backend and can be used if the local Nhost backend was not shut down properly using ctrl + c with nhost dev.
$ nhost down
All Nhost services downFile: src/pages/cli/env-ls.mdx
title: "Nhost env:ls"
nhost env:ls lists all dev environment variables.
$ nhost env:ls
Environment variables for todos-cli.
name value (dev)
FIRST_ENV 22
ANOTHER_ONE myAPIkeyFile: src/pages/cli/env-pull.mdx
title: "Nhost env:pull"
nhost env:pull pulls all dev environment variables to the local environment making the project's dev environment variables available in Hasura and in the Custom API.
$ nhost env:pull
Overwriting existing .env.development file
Downloading development environment variables for project: todos-cli
✅ .env.development file updatedFile: src/pages/cli/index.mdx
title: "Nhost CLI (beta)"
The CLI is your primary interface when doing local development.
Install
$ npm install -g nhostDependencies
The Hasura CLI, Docker and Docker Compose are dependencies, please make sure to have those installed.
Hasura CLI version
Make sure you have at least the version of your Hasura version on Nhost. You can see your Hasura version on Nhost under Settings -> Hasura -> Hasura Version.
Check Hausra CLI
$ hasura versionUpdate your Hasura CLI:
$ hasura update-cli --version [version]You can find Hasura's releases on their GitHub release page.
You might also need to update the cli-ext plugin:
$ hasura plugins cli-ext upgrade --version [version]Since version Hasura version v2.0.0-alpha.5 the cli-ext plugin is a native part of the Hasura CLI and is no longer needed as a plugin.
Usage
USAGE
$ nhost [COMMAND]
COMMANDS
deploy Deploy local migrations to Nhost production
dev Start Nhost project for local development
help display help for nhost
init Initialize current working directory with a Nhost project
login Login to your Nhost account
logout Logout from your Nhost accountWorkflow
# login to your Nhost account
$ nhost login
# initialize working directory as a Nhost project
# please make sure to run it at the root of your project
$ nhost init
# start your development environment (Hasura, HBP, Postgres and Minio)
# Make sure Docker is already running on your machine.
$ nhost devBeta
The CLI is still in beta, so please feel free to send us feedback, bug reports, and issues through the project's repo.
File: src/pages/cli/init.mdx
title: "Nhost Init"
Nhost Init
nhost init initializes a project for local development. Here we are going to initialize a project called todos-cli.
nhost initdoes not yet work on Windows. Follow the GitHub issue.
$ nhost init
? Select Project (Use arrow keys)
---personal---
❯ todos-cli
# Enter to choose the project to initialize
⠇ Initializing todos-cli
Nhost project successfully initializedLet's now look at some of the configuration created.
tree .
.
└── nhost
├── config.yaml
├── metadata
│ ├── actions.graphql
│ ├── actions.yaml
│ ├── allow_list.yaml
│ ├── cron_triggers.yaml
│ ├── functions.yaml
│ ├── query_collections.yaml
│ ├── remote_schemas.yaml
│ ├── tables.yaml
│ └── version.yaml
├── migrations
│ └── 1605871545375_init
│ └── up.sql
└── seeds
└── 1605871559317_roles_and_providers.sql
5 directories, 12 filesThe config.yaml file is where versions and ports can be defined for each service. The versions specified by default are the same running in the production project.
cat nhost/config.yaml
# hasura configuration for the CLI
version: 2
metadata_directory: metadata
# hasura configuration for the project
hasura_graphql_version: v1.3.0.cli-migrations-v2
hasura_graphql_port: 8080
hasura_graphql_admin_secret: 123456
# hasura backend plus configuration
hasura_backend_plus_version: v2.2.0
hasura_backend_plus_port: 9000
# postgres configuration
postgres_version: 12.0
postgres_port: 5432
postgres_user: postgres
postgres_password: postgres
# custom environment variables for Hasura GraphQL engine: webhooks, headers, etc
env_file: ../.env.developmentAlso relevant here, is the init migration that creates a snapshot of the current schema pulled from the project todos-cli and the seeds file which populates the local database with data for both the roles and providers tables used for authentication purposes.
File: src/pages/cli/link.mdx
title: "Nhost Link"
nhost link links an existing directory with an existing Nhost project. Usually after you've cloned a git repository.
$ nhost link
? Select Project (Use arrow keys)
---personal---
❯ todos-cli
Project linked: todos-cliOnce the local directory is linked with a Nhost project, the commands nhost env:ls and nhost env:pull are available.
File: src/pages/cli/login.mdx
title: "Nhost Login"
nhost login allows us to log in to our accounts so that projects associated with them are available to be initialized and started.
nhost login
Enter your email: [email protected]
✔ Email sent to [email protected], please follow the instructions within it
⠼ Waiting for your confirmation
# Check your inbox and follow the instructions
✔ Email Confirmed! You are now logged in.A corresponding nhost logout account is also available.
File: src/pages/custom-api/index.mdx
title: "Custom API (beta)"
Deploy a custom API on Nhost. Also called: Lambda, Serverless Functions, Cloud Functions etc.
To deploy the custom API you must:
- Init your project with the nhost-cli:
nhost init - Connect your repository to your Nhost project under Settings -> Git.
- Have an
api/directory at the root of your project.
Your API will be automatically deployed for every push to the master or the main branch of your repository.
Example code
module.exports = (req, res) => {
const { name = 'World' } = req.query;
res.status(200).send(`Hello ${name}!`);
};Paths
The folder structure inside the api/ folder will resolve the endpoints.
If you're deploying your frontend to Vercel you can add
apito.vercelignoreso the API-folder does not conflict with Vercel's deployment.
Example paths and endpoints
Given this folder and file structure:
|____api
| |____index.js
| |____users
| | |____index.js
| | |____active.js
| |____my-company.jsThe following endpoints will be available:
https://api-str.nhost.app/ - (api/index.js)
https://api-str.nhost.app/users - (api/users/index.js)
https://api-str.nhost.app/users/active - (api/users/active.js)
https://api-str.nhost.app/my-company - (api/my-company.js)Environment variables
System level environment variables are available:
NHOST_HASURA_URL - (Hasura URL)
NHOST_HASURA_ADMIN_SECRET - (Hasura admin secret)
NHOST_HBP_URL - (HBP URL for auth and storage)
NHOST_CUSTOM_API_URL - (Custom API URL)
NHOST_WEBHOOK_SECRET - (Random secret string. Can be used to authenticate requests from Hasura to the Custom API)You can access each environment variable via process.env.<name> ex: process.env.NHOST_HASURA_URL.
All your production environment variables added under Settings -> Environment variables are also added to the custom API.
For local development all system environment variables will point to the local development URLs.
Logs
Logs are not available yet.
Local development
Use the Nhost CLI's nhost dev to start the Custom API locally (together with a full Nhost backend).
TypeScript support
TypeScript is supported.
import { Request, Response } from "express";
export default (req: Request, res: Response) => {
const { name = 'World' } = req.query;
res.status(200).send(`Hello ${name}!`);
};Technical details
We use Google Cloud Run to deploy the Custom API. For now we only allow JavaScript. In the future you will be able to bring your own Dockerfile with any language.
File: src/pages/graphql/index.mdx
title: "GraphQL"
GraphQL is a query language for APIs, which provides a complete and understandable description of the data in your API, gives you the power to ask for exactly what you need.
The GraphQL schema is automatically generated from your tables, which means zero configuration!
GraphQL was first developed by Facebook in 2012 and has since then been widely adopted by companies such as Github, Twitter, Medium, PayPal and others.
File: src/pages/graphql/mutation.mdx
title: "GraphQL Mutation"
GraphQL mutations are used to either insert, update, upsert, or delete data.
Insert
The simplest type of mutation is an insert.
mutation InsertTodo {
insert_todos(
objects: [
{
title: "Delete Firebase account"
body: "Migrate to nhost.io"
done: false
}
]
) {
returning {
id
title
body
done
}
}
}{
"data": {
"insert_todos": [
{
"id": "bf4b01ec-8eb6-451b-afac-81f5058ce852",
"title": "Delete Firebase account",
"body": "Migrate to nhost.io",
"done": true
}
]
}
}Insert multiple rows
You can add multiple rows at once with an array as the objects property. This can be useful for migrating data.
mutation InsertMultipleTodos {
insert_todos(
objects: [
{
title: "Build the front end"
body: "Mobile app or website first?"
done: false
}
{ title: "Launch 🚀", body: "That was easy", done: false }
]
) {
returning {
id
title
body
done
}
}
}Update
You can update your data with a mutation. You can update multiple rows at once, for example marking all todos assigned to yourself as done.
Update mutations are for when you know the data exists. Use an upsert mutation if you want to optionally create or update data depending on whether it exists.
For example, to mark a todo as done, you would use an update mutation, based on the id of your table:
mutation UpdateTodoStatus($id: uuid, $done: Boolean) {
update_todos(_set: { done: $done }, where: { id: { _eq: $id } }) {
returning {
body
done
title
}
}
}Notice how we are using variables as the id and done properties, which lets us mark any todo as done or not done with the same mutation.
Upsert
An upsert mutation will insert an object into the database in case there is no conflict with another row in the table. In case there is a conflict with one or more rows, it will either update the fields of the conflicted rows or ignore the request, depending on your mutation.
In order to convert your insert mutation to an upsert, you need to add an on_conflict property, which tells Hasura which fields it should use to find duplicates.
The
on_conflictkey can only be a unique key in your database.
For an upsert, all columns need to be passed. This is different to an update mutation.
Upsert mutations are for when you are not sure if the data already exists. Use an update mutation if you know it does.
Upsert example
We have set the title to a unique value, which means that you cannot have multiple todos with the same title. If you would like to overwrite the existing data, use a upsert mutation which is constrained to the title:
mutation UpsertTodo {
insert_todos(
objects: {
title: "Delete Firebase account"
body: "Migrate to an nhost.io project"
done: false
}
on_conflict: { constraint: todos_title_key, update_columns: [body, done] }
) {
returning {
body
done
id
title
}
}
}This will update the body and done properties of the todo title to 'Delete Firebase account'.
Conditional upsert
Insert a new object in the article table, or if the primary key constraint todos_title_key is violated, update the columns specified in update_columns only if the provided where condition is met.
For example, you may want to only update an existing todo if it is not done:
mutation UpsertTodo {
insert_todos(
objects: {
title: "Delete Firebase account"
body: "Migrate to an nhost.io project"
done: false
}
on_conflict: {
constraint: todos_title_key
update_columns: [body, done]
where: { done: { _eq: false } }
}
) {
returning {
body
done
id
title
}
}
}Ignore mutation on conflict
If update_columns is empty Hasura ignore the mutation if there is a conflict.
Insert a new object into the author table or, if the unique constraint todos_title_key is violated, ignore the request.
Here we have set the title to a unique key, to prevent multiple tasks with the same name. We want to avoid overwriting existing todos, so the update_columns array is empty:
mutation InsertTodo {
insert_todos(
objects: {
title: "Delete Firebase account"
body: "Migrate to nhost.io"
done: false
}
on_conflict: { constraint: todos_title_key, update_columns: [] }
) {
returning {
body
done
id
title
}
}
}In this case, the insert mutation is ignored because there is a conflict and update_columns is empty.
Delete
To delete your data, use a delete mutation.
This mutation will delete all todos where done is true:
mutation DeleteDoneTodos {
delete_todos(where: { done: { _eq: true } }) {
affected_rows
}
}If you have set up foreign keys which will restrict a delete violation, you will get an error and will not be able to delete the data until all violations are solved. The simplest way to solve this is by set On Delete Violation to CASCADE when you set up a Foreign Key.An
Soft deletes
Sometimes it is better to keep data rather than deleting outright. In this case, you can add a boolean deleted property with a default value of false to your table. When a user deletes data, you set deleted to true.
Use a custom check in the permission settings to stop users from accessing deleted items.
File: src/pages/graphql/query.mdx
title: "GraphQL Query"
You can read data with a GraphQL query. The best way to get started is with the Hasura console or with a GraphQL explorer, like GraphiQL.
Here is an GraphQL query that selects title, body, and done for every row in your todos table.
query GetTodos {
todos {
title
body
done
}
}{
"data": {
"todos": [
{
"title": "Delete Firebase account",
"body": "Migrate to nhost.io",
"done": false
}
]
}
}GraphQL queries can be more complex, with filters, limits, sorting and aggregation.
This GraphQL query selects all items in the todo table that aren't done, with the total number of comments and the last five comments.
query GetTodosWithLatestComments {
todos(where: { done: { _eq: false } }) {
title
body
comments(limit: 5, order_by: { created_at: desc }) {
comment
created_at
author
}
comments_aggregate {
aggregate {
count(columns: id)
}
}
}
}{
"data": {
"todos": [
{
"title": "Delete Firebase account",
"body": "Migrate to nhost.io",
"comments": [
{
"comment": "Let's do this",
"created_at": "2019-10-31T08:34:25.621167+00:00",
"author": "John"
},
{
"comment": "🎉",
"created_at": "2019-10-31T08:33:18.465623+00:00",
"author": "Charlie"
}
],
"comments_aggregate": {
"aggregate": {
"count": 2
}
}
}
]
}
}A great place to get started with setting up queries is in the Hasura Console.
File: src/pages/graphql/subscription.mdx
title: "GraphQL Subscription"
Subscriptions are the same as queries, except they use WebSockets to keep the data up to date in your app. Subscriptions allow realtime updates!
Change query to subscription to create a subscription:
subscription GetTodos {
todos {
title
body
done
}
}That's it!
Your data is always in sync when using subscriptions. It does not matter if the data changes through GraphQL or directly in the database. The data is always syncing in realtime using subscriptions.
File: src/pages/graphql/variables.mdx
title: "GraphQL Variables"
You can send variables to your GraphQL queries and mutations. The variables and the type must be specified, ex: $title: String! or $todo: todos_insert_input!.
mutation($todo: todos_insert_input!) {
insert_todos(objects: [$todo]) {
affected_rows
}
}Because GraphQL is strongly typed you must specify the variable types.
Here is an example using variables with the popular Apollo client.
import { gql, useMutation } from "@apollo/client";
const INSERT_TODO = gql`
mutation($todo: todos_insert_input!) {
insert_todos(objects: [$todo]) {
affected_rows
}
}
`;
function Component() {
//...
const [insertTodo] = useMutation(INSERT_TODO);
//...
return (
<form
onSubmit={async (e) => {
e.preventDefault();
try {
await insertTodo({
variables: {
todo: {
name: todoName,
},
},
});
} catch (error) {
console.error(error);
return alert("Error creating todo");
}
alert("Todo created");
setTodoName("");
}}
>
//...
</form>
);
}File: src/pages/hasura/event-triggers.mdx
title: "Event triggers"
Hasura can trigger webhooks on events (insert, update, delete) happening in the database.
The event in the database does not have to come via the GraphQL API.
As an example, let's imagine you are building a e-commerce system and you want to send an email when for every new order. To achieve that, create an event trigger on insert for the orders table. Now for every new order Hasura will send a webhook with the order-information for all new orders.
In the webhook, you get all order information and can send an email to the customer.
API
You can use any other hosting service to host your API that will receive webhooks from Hasura's event triggers. We recommend Netlify or Vercel because they are easy to get started with.
Security
To make sure that the request your API receives comes from your Hasura instance, and not a hacker, you can use the NHOST_WEBHOOK_SECRET environment variable that is already added to both Hasura and the Custom API.
Set a header x-webhook-secret and use the NHOST_WEBHOOK_SECRET environment variable.
In your API, when you receive the webhook, you can control that the header (x-webhook-secret) to make sure the request is legit.
if (req.headers["x-webhook-secret"] !== process.env.NHOST_WEBHOOK_SECRET) {
return send(res, 401, "incorrect webhook secret");
}
// continue with your webhookFile: src/pages/hasura/index.mdx
title: "Hasura"
Every Nhost project comes with a Hasura. Hasura comes with the GraphQL Engine, Hasura console, event triggers, remote schemas and more.
GraphQL Engine
Hasura automatically generates a GraphQL API based on your PostgreSQL database. This GraphQL handles
- queries (read data).
- mutations (insert, update, delete data).
- subscriptions (read data in realtime).
Hasura Console
In the Hasura console you can manage tables, relations, data, permissions, events and remote schemas.
Event triggers
Send HTTP webhooks based on events (insert, update, delete) in the database.
Remote Schemas
Stitch together a remote GraphQL with the GraphQL API.
File: src/pages/hasura/permissions.mdx
title: "Permissions"
The GraphQL API is protected by a permission system using roles. The permission system is based on JWT tokens. The user gets a JWT token upon login. This JWT token is being sent as a header with all GraphQL API requests to authenticate the user so correct permissions can be applied.
The JWT-token contains specific user information, such as user id. In the permission system in Hasura you can specify what a user is allowed to do based on ex the user id in the JWT-token and the data.
A common use-case is to allow a user read something if user_id in the database is equal to the user id (x-hasura-user-id) in the JWT token.
By default, every user has the following session variables that can be used when creating permission rules:
x-hasura-user-id
x-hasura-allowed-roles
x-hasura-default-roleThe default role for a user is
user.
You can add more session variables (like users.company_id) manually with custom user columns.
You handle permissions for a table in the Permissions tab.
Insert permission
Here is a popular approach for insert permission for logged in users. We'll break it down below the image.
- Use the role user and click on the Insert column.
- Select Without any checks.
- Select the columns you want the user to insert. Here we'll only select
namesince the other columns will be filled in with default values and we don't want users to override them. - For column presets we select
user_idto be the value ofx-hasura-user-id(the user's id). This way, we make sure that theuser_idis securely set to the id of the user making the request.
Select permission
Below follows a popular approach for select permission for logged in users. We'll break it down below the image.
- Use the role user and click on the Select column.
- Select With custom check and create the rule that users can only read rows where
user_id = x-hasura-user-id. In other words, users can only read their own data. - Limit number of rows to 100 (or some other relevant number).
- Select the columns you want the user to be able to read. In our case we'll allow the user to read all columns.
Update and Delete permission
Update and Delete permissions usually follows the same rules as Select permissions.
File: src/pages/hasura/remote-schemas.mdx
title: "Remote schemas"
With Hasura remote schemas, you can add any external GraphQL API to your GraphQL API.
You can test this by adding this Pokémon GraphQL API: https://graphql-pokemon.now.sh.
Now you can query Pokémons in your API:
query {
pokemons(first: 3) {
id
name
image
attacks {
fast {
damage
name
type
}
special {
damage
name
type
}
}
}
}File: src/pages/hasura/tables.mdx
title: "Tables"
You can manage your PostgreSQL database tables directly in the Hasura console.
Create table
Creating a table in the Hasura Console (recommended) does two things:
- Creates the table in PostgreSQL.
- Hasura tracks the table and automatically generates a GraphQL API.
You can also create tables directly in PostgreSQL.
How to create a table
- In the Hasura Console. Click on the Add table in the left menu.
- Choose a Table name.
- Add your Columns.
- Add a Primary Key. Usually this is your
idcolumn. - (Optional) Add Foreign Keys if any.
- (Optional) Add Unique Keys.
- (Optional) Add a comment.
- Click Add table.
Frequently used columns
Frequently used columns are a quick way to add frequently used columns.
id (integer)
Creates a column called id with an Integer type. The column automatically increments for every row.
Example values: 1,2,3.
Why use Integers?
Integers can be sorted based on when the row was created. They are also easier to remember and debug.
id (bigint)
Same as id (integer) but can hold larger values.
id (UUID)
Creates a column called id with an UUID type. The column automatically generates a new UUID for every row.
Example value: cc3d4bc2-39a8-4efc-9ef0-c71b899d113a
Why use UUID?
UUIDs does not leak information about the number of rows in your database. Using a UUID also means that one ID is unique across all databases in the world.
created_at
Automatically sets the row to the date of creation. The value will stay the same if a row is updated. The date is a timestamp with timezone data.
Example value: 2019-09-23T15:07:09.548632+00:00
updated_at
Automatically sets the row to the date of creation. This will update to the current date every time a row is updated. The date is a timestamp with timezone data.
Example value: 2019-09-23T15:07:09.548632+00:00
Modify table
- Click on the table you want to edit in the left menu.
- Click on Modify.
- Modify the table column.
Delete table
- Click on the table you want to delete in the left menu.
- Click on Modify.
- Scroll to the bottom and click on Delete table.
- Type the name of the table you want to delete and click OK to confirm deletion.
The table you want to delete might have existing relationships tracked by Hasura. When you try to delete such a table you will be asked to extra confirmation to delete those relationships too.
Track tables
If you create tables directly into PostgreSQL (not via the Hasura console) you must track those tables in the Hasura console. When you track a table the GraphQL API gets updated automatically to include the fields.
- Click on Data in the top menu.
- A list of untracked tables is presented.
- Click Track All (recommended) or click Track for each table you want to track.
Track foreign-key relations
- Click on Data in the top menu.
- A list of untracked foreign-key relations is presented.
- Click Track All (recommended) or click Track for each relationship you want to track.
File: src/pages/libraries/nhost-js-sdk.mdx
title: "Nhost JS SDK"
JavaScript SDK for handling Auth and Storage
Installation
$ npm install nhost-js-sdkor
$ yarn add nhost-js-sdkSetup
import { createClient } from "nhost-js-sdk";
const nhostClient = createClient({
baseURL: "https://backend-url.nhost.app",
})
const auth = nhostClient.auth;
const storage = nhostClient.storage;
export { auth, storage };| Parameters | ||||
|---|---|---|---|---|
| baseURL | string | required | Backend URL | |
| useCookies | boolean | optional | Use cookies for auth (defaults to false) |
|
| refreshIntervalTime | number | optional | Autorefresh interval in ms (defaults to Math.max(30 * 1000, JWTExpiresIn - 45000)) |
|
| ssr | boolean | optional | Enable server-side rendering mode (defaults to typeof window === "undefined") |
|
| autoLogin | boolean | optional | Auto logins user (defaults to true) |
|
| clientStorage | ClientStorage | optional | Storage to use (defaults to window.localStorage) |
|
| clientStorageType | string | optional | Storage type (defaults to web) |
Usage
import { auth, storage } from "src/utils/nhost";Auth
Creating a user with email and password is done with register(). For OAuth, the login() function creates the user on the first login attempt.
auth.register({ email, password });
// with options
auth.register({
email,
password,
options: { userData: { display_name: "Joe Doe" } },
});Existing users can login with email and password or using an OAuth provider.
auth.login({ email, password });
// OAuth provider: google | github | linkedin ...
auth.login({ provider })Users can logout via logout().
auth.logout();To register a callback to run when the auth state changes (e.g., when a user logs out) is done via onAuthStateChanged().
auth.onAuthStateChanged((loggedIn) => {
console.log("auth state changed!");
console.log({ loggedIn });
});To check if a user is logged in or not use isAuthenticated().
auth.isAuthenticated();notes:
truemeans the user is logged infalsemeans the user is not logged innullmeans that a request to login the user is ongoing.
For getting a user session's JWT Token use getJWTToken().
auth.getJWTToken();And for its claims getClaim().
auth.getClaim("x-hasura-user-id");To activate a user account use activate().
auth.activate(<ticket>);For changing an account's email address without confirmation use changeEmail(). The user must be logged in.
auth.changeEmail(new_email);Or with a confirmation email requestEmailChange().
auth.requestEmailChange(new_email);To confirm the email change use confirmEmailChange() and pass in the ticket received with the email.
auth.confirmEmailChange(ticket);For changing a user's password use changePassword().
auth.changePassword(oldPassword, newPassword);Or with a confirmation email use requestPasswordChange().
auth.requestPasswordChange(email);To confirm the password change use confirmPasswordChange().
auth.confirmPasswordChange(new_password, ticket);To generate a MFA QR-code use MFAGenerate(). The user must be logged in.
auth.MFAGenerate();Enabling MFA is done via enable().
auth.enable(code);And disabling is done via disable().
auth.disable(code);To login using MFA TOTP use MFATotp() and pass in the ticket returned by login() (when MFA is enabled).
auth.MFATotp(code, ticket);Storage
Uploading a file is done using put().
storage.put(path, file, metadata?, onUploadProgress?);To delete a file use delete().
storage.delete(path);To get the metadata for a file use getMetadata().
storage.getMetadata(path);Other environments
React Native
Install:
$ yarn add @react-native-community/async-storageInstallation instructions can be found at https://react-native-community.github.io/async-storage/docs/install.
import createClient from "nhost-js-sdk";
import AsyncStorage from "@react-native-async-storage/async-storage";
const nhostClient = createClient({
baseURL: "https://backend-url.nhost.app",
clientStorage: AsyncStorage,
clientStorageType: "react-native",
});
const { auth, storage } = nhostClient;
export { auth, storage };Ionic
import createClient from "nhost-js-sdk";
import { Plugins } from "@capacitor/core";
const { Storage } = Plugins;
const nhostClient = createClient({
baseURL: "https://backend-url.nhost.app",
clientStorage: Storage,
clientStorageType: "capacitor
})
const auth = nhostClient.auth;
const storage = nhostClient.storage;
export { auth, storage };Expo
Install:
$ expo install expo-secure-storeMore info: https://docs.expo.io/versions/latest/sdk/securestore/.
import createClient from "nhost-js-sdk";
import * as SecureStore from "expo-secure-store";
const nhostClient = createClient({
baseURL: "https://backend-url.nhost.app",
clientStorage: SecureStore,
clientStorageType: "expo-secure-storage",
})
const auth = nhostClient.auth;
const storage = nhostClient.storage;
export { auth, storage };File: src/pages/libraries/nhost-nuxt.mdx
title: "@nhost/nuxt"
NuxtJS module to use Nhost's GraphQL, authentication and storage in a NuxtJS app.
Installation
Using NPM:
$ npm install --save @nhost/nuxtor using Yarn:
$ yarn add @nhost/nuxtOther recommended NPM packages:
nhost-js-sdk @nuxtjs/apollo graphql-tag
Add the module and configure nhost in nuxt.config.js:
{
modules: [
'@nhost/nuxt'
],
nhost: {
baseURL: "https://backend-REPLACE.nhost.app"
// optional other nhost-js-sdk setup options
}
}Check out our documentation for all the available nhost-js-sdk options.
Middleware
This module provides a middleware that automatically handles auth guards to protect pages.
- Add
nhost/authto your Nuxt config middleware:
{
router: {
middleware: ["nhost/auth"];
}
}- Define the
homeandlogoutroutes on the nhost config
{
nhost: {
routes: {
home: '/dashboard',
logout: '/'
}
}
}Users who attempt to access auth guarded pages without being logged in gets redirected to the logout route, and users who are logged in and attempt to access guest pages gets redirected to the home route.
These routes can also be set to either undefined or false which will disable their respective functionality. They are also available under this.$nhost.$options.routes on Vue components or ctx.$nhost.$options.routes on the Nuxt Context.
- Finally, add an
nhostAuthproperty to your Nuxt pages:
export default {
nhostAuth: true,
};nhostAuth takes the following values:
true: Users must be authenticated to access this page. If the user is not authenticated, the user gets redirected to thelogoutroute.false: This is the default value; no authentication required.'guest': This page can only be accessed by unauthenticated users. Users who are logged in will be redirected to thehomeroute.
Typescript
If you're using Typescript, make sure to include @nhost/nuxt to your Typescript config types:
{
compilerOptions: {
types: ["@nhost/nuxt"];
}
}Nuxt-apollo (@nuxtjs/apollo)
To use this library with @nuxtjs/apollo, create two Nuxt plugins: nuxt-apollo-config.js and nhost-apollo-ws-client.js inside your plugins folder with the following content:
export default (ctx) => {
return {
httpEndpoint: "https://hasura-<REPLACE>.nhost.app/v1/graphql",
wsEndpoint: "wss://hasura-<REPLACE>.nhost.app/v1/graphql",
getAuth: () => {
const token = ctx.$nhost.auth.getJWTToken();
return token ? `Bearer ${token}` : null;
},
};
};export default (ctx) => {
const subscriptionClient = ctx.app.apolloProvider.defaultClient.wsClient;
ctx.$nhost.auth.onAuthStateChanged((state) => {
if (subscriptionClient.status === 1) {
subscriptionClient.close();
subscriptionClient.tryReconnect();
}
});
ctx.$nhost.auth.onTokenChanged(() => {
if (subscriptionClient.status === 1) {
subscriptionClient.tryReconnect();
}
});
};Then, in your Nuxt config:
{
[...]
"plugins": [
{
"src": "~/plugins/nhost-apollo-ws-client.js",
"mode": "client"
}
],
"apollo": {
"clientConfigs": {
"default": "~/plugins/nhost-apollo-config.js"
}
},
[...]
}Usage
This module exposes an $nhost property on the Vue object and on the NuxtJS Context, which is an instance of NhostClient.
Example
You can find a full example project with this library and @nuxtjs/apollo here.
File: src/pages/libraries/react-apollo.mdx
title: "@nhost/react-apollo"
Make it easy to use Apollo and React with your Nhost project.
@nhost/react-apollo exposes one component: NhostApolloProvider. Wrap your whole App around this component to make GraphQL requests anywhere from your app.
It's recommended to use @nhost/react-apollo with @nhost/react-auth and nhost-js-sdk for users to login and interact with the GraphQL API.
If you use @nhost/react-apollo and NhostApolloProvider without authentication (auth property) all GraphQL requests will be made with the public role.
Install
npm install @nhost/[email protected] @nhost/[email protected] nhost-js-sdk @apollo/client graphql@apollo/client is required for @nhost/react-apollo to work and will be used to import useQuery, useMutation, and useSubscription.
Initialize the Nhost client
import { createClient } from "nhost-js-sdk";
const config = {
baseURL: "https://backend-REPLACE.nhost.app",
};
const { auth, storage } = createClient(config);
export { auth, storage };Read more about nhost-js-sdk here.
Create React App
See full Nhost Create React App with Apollo.
import React from "react";
import ReactDOM from "react-dom";
import { NhostAuthProvider } from '@nhost/react-auth";
import { NhostApolloProvider } from "@nhost/react-apollo";
import { auth } from "utils/nhost";
import App from "./App";
ReactDOM.render(
<React.StrictMode>
<NhostAuthProvider auth={auth}>
<NhostApolloProvider
auth={auth}
gqlEndpoint={`https://hasura-REPLACE.nhost.app/v1/graphql`}
>
<App />
</NhostApolloProvider>
</NhostAuthProvider>
</React.StrictMode>,
document.getElementById("root")
);Next.js
See full Nhost Next.js with Apollo.
import { NhostAuthProvider } from "@nhost/react-auth";
import { NhostApolloProvider } from "@nhost/react-apollo";
import { auth } from "utils/nhost";
function MyApp({ Component, pageProps }) {
return (
<NhostAuthProvider auth={auth}>
<NhostApolloProvider
auth={auth}
gqlEndpoint={`https://hasura-REPLACE.nhost.app/v1/graphql`}
>
<Component {...pageProps} />
</NhostApolloProvider>
</NhostAuthProvider>
);
}
export default MyApp;Learn more about auth and storage in the nhost-js-sdk library.
Usage
Since the NhostApolloProvider is now installed it's possible to use the Apollo client with its useQuery, useSubscription, and useMutation functions anywhere in your app.
import { useQuery, gql } from "@apollo/client";
const GET_TODOS = gql`
query {
todos {
id
created_at
name
completed
}
}
`;
export function App() {
const { loading, data } = useQuery(GET_TODOS);
if (loading) {
return <div>Loading..</div>;
}
return (
<div>
<h1>in app</h1>
{!data ? (
"no data"
) : (
<ul>
{data.todos.map((todo) => {
return <li key={todo.id}>{todo.name}</li>;
})}
</ul>
)}
</div>
);
}Options
Options and parameters for NhostApolloProvider.
| Parameter | Description | Required |
|---|---|---|
auth |
The auth object from the Nhost Client |
Yes |
gqlEndpoint |
GraphQL API Endpoint | Yes |
headers |
Custom headers. | No |
publicRole |
Public role that Apollo will automatically use if the user is not logged in. Will default to public. |
No |
cache |
Customized cache. More info. | No |
connectToDevTools |
Enable Apollo Dev tools. More info | No |
onError |
Custom function for error handling with Apollo link. More info | No |
File: src/pages/libraries/react-auth.mdx
title: "@nhost/react-auth"
Make it easy to use Authentication with your Nhost project.
@nhost/react-auth exposes one component: NhostAuthProvider. Wrap your whole App around this component to manage if users are logged in or not.
It's recommended to use @nhost/react-auth with @nhost/react-apollo and nhost-js-sdk for users to login and interact with the GraphQL API.
Install
npm install @nhost/[email protected]Create React App
Setup
See full Nhost Create React App with Apollo.
import React from "react";
import ReactDOM from "react-dom";
import { NhostAuthProvider } from "@nhost/react-auth";
import { NhostApolloProvider } from "@nhost/react-apollo";
import { auth } from "utils/nhost";
import App from "./App";
ReactDOM.render(
<React.StrictMode>
<NhostAuthProvider auth={auth}>
<NhostApolloProvider
auth={auth}
gqlEndpoint={`https://hasura-REPLACE.nhost.app/v1/graphql`}
>
<App />
</NhostApolloProvider>
</NhostAuthProvider>
</React.StrictMode>,
document.getElementById("root")
);Usage
First, we'll create an AuthGate component. Only logged-in users are allowed through this component.
import React from "react";
import { Redirect } from "react-router-dom";
import { useAuth } from "@nhost/react-auth";
export function AuthGate({ children }) {
const { signedIn } = useAuth();
if (signedIn === null) {
return <div>Loading...</div>;
}
if (!signedIn) {
return <Redirect to="/login" />;
}
// user is logged-in
return children;
}We'll wrap routes that we want to protect for logged-in users only using this component. In the example below, only logged-in users are allowed to access the /-route.
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import { NhostAuthProvider } from '@nhost/react-auth";
import { NhostApolloProvider } from "@nhost/react-apollo";
import { auth } from "utils/nhost";
import { AuthGate } from "components/auth-gate"
import App from "./App";
ReactDOM.render(
<React.StrictMode>
<NhostAuthProvider auth={auth}>
<NhostApolloProvider
auth={auth}
gqlEndpoint={`https://hasura-REPLACE.nhost.app/v1/graphql`}
>
<Router>
<Switch>
<Route exact path="/login">
<Login />
</Route>
<AuthGate>
<Route exact path="/">
<App />
</Route>
</AuthGate>
</Switch>
</Router>
</NhostApolloProvider>
</NhostAuthProvider>
</React.StrictMode>,
document.getElementById("root")
);Next.js
See full Nhost Next.js with Apollo.
Setup
import { NhostAuthProvider } from "@nhost/react-auth";
import { NhostApolloProvider } from "@nhost/react-apollo";
import { auth } from "utils/nhost";
function MyApp({ Component, pageProps }) {
return (
<NhostAuthProvider auth={auth}>
<NhostApolloProvider
auth={auth}
gqlEndpoint={`https://hasura-REPLACE.nhost.app/v1/graphql`}
>
<Component {...pageProps} />
</NhostApolloProvider>
</NhostAuthProvider>
);
}
export default MyApp;Usage
First we'll create a PrivateRoute component. We'll wrap pages that only logged in users are allowed to access.
import { useAuth } from "@nhost/react-auth";
export function PrivateRoute(Component) {
return () => {
const { signedIn } = useAuth();
// wait to see if the user is logged in or not.
if (signedIn === null) {
return <div>Checking auth...</div>;
}
if (!signedIn) {
return <div>Login form or redirect to `/login`.</div>;
}
return (<Component {...arguments} />);
};
}Usage
import { PrivateRoute } from "components/private-route";
function Home() {
return <div>My dashboard</div>;
}
export default PrivateRoute(Dashboard);File: src/pages/postgresql/index.mdx
title: "PostgreSQL"
PostgreSQL is the world's most advanced open source database and every Nhost project has its own Postgres database.
Version
The following versions of PostgreSQL is supported when creating a Nhost project:
- 12.0
- 11.5
- 10.5
- 9.6
Prepared tables
Each newly created project's database have the following prepared tables:
public.usersauth.account_providersauth.account_rolesauth.accountsauth.providersauth.refresh_tokensauth.roles
Direct Access
Every Postgres instance comes pre-configured with an SSL certificate
The PostgreSQL database can be accessed directly with a Postgres client such as pgAdmin, DBeaver, or Table Plus. Find the Postgres connection string in the Nhost console in the Postgres tab.
File: src/pages/quick-start/authentication.mdx
title: "Authentication"
Currently, everyone can select and insert todos. That is ok to start with but now it's time to secure our API by adding authentication.
Authentication is built-in to Nhost and easy to get started.
Create User
Let's first create our user by going to the project's Auth section.
You will use this user to access your todos later on. But first, we will install nhost-js-sdk to help us with authentication.
Install and Configure nhost-js-sdk
nhost-js-sdkis an open-source JavaScript library for interacting with Nhost's auth and storage.
$ npm install nhost-js-sdkNow, we need to configure and initialize a connection to Nhost. Create a new folder, src/utils, and create nhost.js inside it.
import { createClient } from "nhost-js-sdk";
const config = {
baseURL: "https://backend-REPLACE.nhost.app",
};
const { auth, storage } = createClient(config);
export { auth, storage };Please don't forget to replace https://backend-REPLACE.nhost.app with your endpoint.
Add NhostAuthProvider
Now it's time to wrap our App with another provider. The NhostAuthProvider. This provider indicates if a user is logged in or not in the AuthGate component that we'll implement soon.
First let's install the @nhost/react-auth package:
$ npm install @nhost/[email protected]We'll also pass our auth object as a prop to both providers, NhostAuthProvider and NhostApolloProvider, so that the whole app can share the same auth state.
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { NhostApolloProvider } from "@nhost/react-apollo";
import { NhostAuthProvider } from "@nhost/react-auth";
import { auth } from "utils/nhost";
ReactDOM.render(
<React.StrictMode>
<NhostAuthProvider auth={auth}>
<NhostApolloProvider
auth={auth}
gqlEndpoint="https://hasura-REPLACE.nhost.app/v1/graphql"
>
<App />
</NhostApolloProvider>
</NhostAuthProvider>
</React.StrictMode>,
document.getElementById("root")
);Now it's a good time to recap what has been done so far in this chapter. We started by creating a new user for our Nhost project, followed by installing and configuring nhost-js-sdk. Finally we wrapped our app with NhostAuthProvider, passing auth as a prop so that the same auth object gets used in both Nhost providers.
Now, let's create the login component.
Login
We need to write a login component that handles the form's submission of a user's data and some scaffolding for the app routes.
Create a directory src/components and create the Login component with the following content:
import React, { useState } from "react";
import { useHistory } from "react-router-dom";
import { auth } from "utils/nhost";
export function Login(props) {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const history = useHistory();
async function handleSubmit(e) {
e.preventDefault();
// login
try {
await auth.login({email, password});
} catch (error) {
alert("error logging in");
console.error(error);
return;
}
// redirect back to `/`
history.push("/");
}
return (
<div>
<h1>Login</h1>
<form onSubmit={handleSubmit}>
<input
type="email"
placeholder="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<input
type="password"
placeholder="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button>Login</button>
</form>
</div>
);
}Now let's create two routes. One for / where we'll have our todos listed, and one for /login where users can log in.
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { Login } from "components/login";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import { NhostApolloProvider } from "@nhost/react-apollo";
import { NhostAuthProvider } from "@nhost/react-auth";
import { auth } from "utils/nhost";
ReactDOM.render(
<React.StrictMode>
<NhostAuthProvider auth={auth}>
<NhostApolloProvider
auth={auth}
gqlEndpoint="https://hasura-REPLACE.nhost.app/v1/graphql"
>
<Router>
<Switch>
<Route exact path="/login">
<Login />
</Route>
<Route exact path="/">
<App />
</Route>
</Switch>
</Router>
</NhostApolloProvider>
</NhostAuthProvider>
</React.StrictMode>,
document.getElementById("root")
);Finally, add two links to App.js. One to /login and one that will logout the user.
import React, { useState } from "react";
import { gql, useSubscription, useMutation } from "@apollo/client";
import { Link } from "react-router-dom";
import { auth } from "utils/nhost";
const GET_TODOS = gql`
subscription {
todos {
id
created_at
name
completed
}
}
`;
const INSERT_TODO = gql`
mutation($todo: todos_insert_input!) {
insert_todos(objects: [$todo]) {
affected_rows
}
}
`;
function App() {
const { data, loading } = useSubscription(GET_TODOS);
const [insertTodo] = useMutation(INSERT_TODO);
const [todoName, setTodoName] = useState("");
if (loading) {
return <div>Loading</div>;
}
return (
<div>
<Link to="/login">Login</Link>
<div onClick={() => auth.logout()}>Logout</div>
<div>
<form
onSubmit={async (e) => {
[...]
</form>
</div>
{!data ? (
"no data"
) : (
<ul>
{data.todos.map((todo) => {
return <li key={todo.id}>{todo.name}</li>;
})}
</ul>
)}
</div>
);
}
export default App;Phew, that's a lot of code. Let's stop here and recap what we have.
Both routes (/ and /login) are still unprotected. We'll fix that soon. When we visit / we still see all todos. That's fine for now since we allow the public role to select them.
On the other hand, when we log in, we see No data. And if we log out again, our todos are back. What's going on?
When logged out, the public role was used, which still had permissions to select todos. When we log in, the user role was used instead, which doesn't have permission to select todos. Yet!
Things we need to do to secure our app:
- Protect the
/route so that it is available to logged-in users only. - Remove all permissions for the
publicrole. Only logged-in users can insert and select todos. - Add
user_idto thetodostable so that every todo belongs to a user. - Set permissions so that users are only allowed to insert and select their todos.
Auth Gate
Let's now create the AuthGate component that ensures only authenticated users can access specific routes.
import { Redirect } from "react-router-dom";
import { useAuth } from "@nhost/react-auth";
export function AuthGate({ children }) {
const { signedIn } = useAuth();
if (signedIn === null) {
return <div>Loading...</div>;
}
if (!signedIn) {
return <Redirect to="/login" />;
}
return children;
}AuthGate checks whether a user is logged in and redirects to /login otherwise.
In src/index.js we add the AuthGate component around the routes we want only logged-in users to access.
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { Login } from "components/login";
import { AuthGate } from "components/auth-gate";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import { NhostApolloProvider } from "@nhost/react-apollo";
import { NhostAuthProvider } from "@nhost/react-auth";
import { auth } from "utils/nhost";
ReactDOM.render(
<React.StrictMode>
<NhostAuthProvider auth={auth}>
<NhostApolloProvider
auth={auth}
gqlEndpoint="https://hasura-xxx.nhost.app/v1/graphql"
>
<Router>
<Switch>
<Route exact path="/login">
<Login />
</Route>
<AuthGate>
<Route exact path="/">
<App />
</Route>
</AuthGate>
</Switch>
</Router>
</NhostApolloProvider>
</NhostAuthProvider>
</React.StrictMode>,
document.getElementById("root")
);Now users can only access / if they are logged-in.
Connect todos to users
A todo should belong to a user and only be visible to that user. Before we do that all existing todos must be deleted. To delete all existing todos, click Browse Rows for the todos table, select all todos and click on the trash can icon.
That deleted all todos. Now let's add the new user_id column.
Click on Modify and then click the button Add a new column. Name the new column user_id with the type UUID. Make sure to uncheck Nullable. Click Save.
user_idrefers to the id of the user the todo belongs to.
For our GraphQL API and database to understand that the user_id refers to the user's id we need to create a Foreign Key.
A Foreign key is a link between two tables
The foreign key links the users table to the todos table.
You create the foreign key in the same Modify tab. Scroll down a bit to Foreign Keys and click Add. See the image below of how to set the Foreign Key and click Save.
For Hasura to use the foreign key relation in the GraphQL API, we need to track the foreign key relationship. Go to the DATA tab and click Track all.
Permissions for users
Remove permissions for the public role
We won't use the public role anymore, so let's remove all permission for that role. See the image below.
Now we'll add permissions for the user role.
All logged-in users have the
userrole.
Insert
First, we'll set the Insert permission.
A user can only insert name because all other columns will be set automatically. More specifically, user_id will be set to the id of the user making the request (x-hasura-user-id) and is configured in the Column presets section. See the image below.
Select
For Select permission, set a custom check so users can only select todos where user_id is the same as their user id. In other words: users are only allowed to select their own todos. See the image below.
The app is completed
You have probably already tested your app during this guide, but you have finished you're app. Congratulations!
File: src/pages/quick-start/deploy-app.mdx
title: "Deploy app"
Your app is ready to be deployed. You only need to deploy your frontend app since the backend is already deployed and production-ready at Nhost.
You can use your favorite platform to deploy your frontend app. We recommend using Netlify or Vercel, or Stormkit because they are performant and easy to use.
For a more complete list of hosting alternatives, check the Create React App Deployment.
File: src/pages/quick-start/frontend-web-app.mdx
title: "Frontend Web App"
At this point, we have all the backend (database table, permissions, and an API) needed to store and retrieve todos from any frontend app. It is now time to start building the actual frontend app!
React app
We are going to use create-react-app because it is the easiest way to get started, and it offers a modern build setup with no configuration.
Create a new react app:
$ npx create-react-app nhost-todosOnce completed, change the directory to the new project and install these packages. The packages will be used to send GraphQL requests to the Nhost backend.
$ cd nhost-todos
$ npm install @nhost/[email protected] @apollo/client graphql react-router-domCreate a jsconfig.json file in the root of your project to make imports easier.
{
"compilerOptions": {
"baseUrl": "src"
},
"include": ["src"]
}Start the app.
$ npm startAdd NhostApolloProvider to index.js
NhostApolloProvider comes from @nhost/react-apollo and deals with all the boilerplate for having ApolloClient configured to communicate securely with Nhost.
Import NhostApolloProvider and wrap App so that GraphQL is available everywhere in your app.
Replace
https://hasura-xxx.nhost.app/v1/graphqlwith your project's GraphQL API endpoint. You can find the endpoint in your project's dashboard.
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { NhostApolloProvider } from "@nhost/react-apollo";
ReactDOM.render(
<React.StrictMode>
<NhostApolloProvider gqlEndpoint="https://hasura-xxx.nhost.app/v1/graphql">
<App />
</NhostApolloProvider>
</React.StrictMode>,
document.getElementById("root")
);Now we can fetch data!
Fetch data using GraphQL in App.js
Replace all content of src/App.js with the following code:
import React from "react";
import { gql, useQuery } from "@apollo/client";
const GET_TODOS = gql`
query {
todos {
id
created_at
name
is_completed
}
}
`;
function App() {
const { data, loading } = useQuery(GET_TODOS);
if (loading) {
return <div>Loading</div>;
}
return (
<div>
{!data ? (
"No todos"
) : (
<ul>
{data.todos.map((todo) => {
return <li key={todo.id}>{todo.name}</li>;
})}
</ul>
)}
</div>
);
}
export default App;Your two previous todos now appear in your app.
Let's move on and add functionality to insert new todos.
Insert todos
We need to create a GraphQL mutation and add a simple form for inserting todos.
import React, { useState } from "react";
import { gql, useQuery, useMutation } from "@apollo/client";
const GET_TODOS = gql`
query {
todos {
id
created_at
name
is_completed
}
}
`;
const INSERT_TODO = gql`
mutation($todo: todos_insert_input!) {
insert_todos(objects: [$todo]) {
affected_rows
}
}
`;
function App() {
const { data, loading } = useQuery(GET_TODOS);
const [insertTodo] = useMutation(INSERT_TODO);
const [todoName, setTodoName] = useState("");
if (loading) {
return <div>Loading</div>;
}
return (
<div>
<div>
<form
onSubmit={async (e) => {
e.preventDefault();
try {
await insertTodo({
variables: {
todo: {
name: todoName,
},
},
});
} catch (error) {
console.error(error);
return alert("Error creating todo");
}
alert("Todo created");
setTodoName("");
}}
>
<input
type="text"
placeholder="todo"
value={todoName}
onChange={(e) => setTodoName(e.target.value)}
/>
<button>Create todo</button>
</form>
</div>
{!data ? (
"no data"
) : (
<ul>
{data.todos.map((todo) => {
return <li key={todo.id}>{todo.name}</li>;
})}
</ul>
)}
</div>
);
}
export default App;Great! We can now insert new todos. But for the new todos to be listed, we have to reload the page ourselves. We can do better, so let's fix that by introducing subscriptions.
Subscriptions updates data in realtime. When the data changes in the database, our app instantly updates and shows the newest data. It's almost like magic!
Get data in realtime using subscriptions
We'll replace useQuery with useSubscription and query with subscription in GET_TODOS.
import React, { useState } from "react";
import { gql, useSubscription, useMutation } from "@apollo/client";
const GET_TODOS = gql`
subscription {
todos {
id
created_at
name
is_completed
}
}
`;
const INSERT_TODO = gql`
mutation($todo: todos_insert_input!) {
insert_todos(objects: [$todo]) {
affected_rows
}
}
`;
function App() {
const { data, loading } = useSubscription(GET_TODOS);
const [insertTodo] = useMutation(INSERT_TODO);
const [todoName, setTodoName] = useState("");
if (loading) {
return <div>Loading</div>;
}
return (
<div>
<div>
<form
onSubmit={async (e) => {
e.preventDefault();
try {
await insertTodo({
variables: {
todo: {
name: todoName,
},
},
});
} catch (error) {
alert("Error creating todo");
console.error(error);
return;
}
alert("Todo created");
setTodoName("");
}}
>
<input
type="text"
placeholder="todo"
value={todoName}
onChange={(e) => setTodoName(e.target.value)}
/>
<button>Add todo</button>
</form>
</div>
{!data ? (
"no data"
) : (
<ul>
{data.todos.map((todo) => {
return <li key={todo.id}>{todo.name}</li>;
})}
</ul>
)}
</div>
);
}
export default App;Perfect! New todos get listed in realtime when we create them. You can even open two tabs, add new todos in one tab and see how all windows display the latest data.
Your app has superpowers!
Next
File: src/pages/quick-start/index.mdx
title: "Introduction"
import { YouTube } from "components/youtube";
Todo App
We are going to build a todo app using Nhost and React. We use React here, but Nhost is framework agnostic, so you can use whatever framework you like.
We will start by creating a backend on Nhost, followed by a React client application for managing todos. We will take an incremental approach to showcase Nhost's authentication system.
As you follow this tutorial make sure to install the correct SDK dependencies for the current version of Nhost. Throughout this tutorial both
@nhost/react-apolloand@nhost/react-authare meant to be used on their version 1 (@nhost/[email protected]and@nhost/[email protected]or lower releases) while version 2 of these packages are exclusive for the upcoming Nhost v2, now on private beta: Nhost v2 Private Beta waitlist
Help
The quick start is simple enough that we expect you not to find any issues, but if you do, please reach out to us on Discord or at [email protected].
Next step
File: src/pages/quick-start/nhost-backend.mdx
title: "Nhost Backend"
Before we start:
- Please make sure you have NodeJS and npm installed.
- If you haven't already, please create an account on Nhost.
Create Project
We will create a project that will serve as the backend to our todo client application. Please log in to Nhost, click on Add new project. Select a name and location and click Create project.
Nhost now creates your backend. It should take less than a minute. Once completed, go to your project to see your project's dashboard.
That's it! We now have all the necessary backend infrastructure to build our app.
But first, we need to create a table and set permissions to store our todos.
Next step
File: src/pages/quick-start/todos-table.mdx
title: "Todos Table"
We need a table to store the todos that our application will manage, and also correct permissions on querying and inserting them. Hasura Console to the rescue.
Hasura Console is where you manage your project's database and GraphQL API.
Hasura Console
Go to Hasura. Go ahead and copy Hasura's Admin secret and use it to login to the Hasura console.
If you are not familiar with the Hasura Console, now it is a good time to click around and explore.
Create todos table
Click on DATA tab on the top menu, and click on Create Table.
We'll create a todos table:
| Name | Type | Default value |
|---|---|---|
| id | UUID | gen_random_uuid() |
| created_at | Timestamp | now() |
| name | Text | |
| is_completed | Boolean | false |
For the id and created_at you should use the Frequently used columns option. This ensures correct name, type and default values for those columns. It's a quick way to get started creating a new table.
The other columns, name and is_completed, you need to add manually (see the image bellow). Make sure you set the default value for is_completed to false.
Scroll down and click Add table.
Query Data
To make sure we have a working GraphQL API, we will now insert two todos. Click the Insert Row tab and create two todos by specifying a name and clicking on Save (see the animation bellow).
Once you've added the todos, go the GraphiQL tab to query the data. Copy-and-paste the following query and click the play button.
query {
todos {
id
created_at
name
is_completed
}
}You should see your todos to the right as a response.
Permissions
Right now, the only way to query todos is by using the admin role with an admin-secret (x-hasura-admin-secret). We are going to change that behavior so that anyone (public) is able to insert and select todos.
In the Hasura Console, go to your todos table and click on Permissions. We will set insert and select permissions for the public role.
The
publicrole is used by users that are not logged in.
Insert
Row insert permissions should be set to Without any checks and Column insert permissions should only have name checked.
Select
Row select permissions should be set to Without any checks and Column select permissions should have all columns checked.
Anyone can now insert and select todos. This is not secure, but it's a good start. We will secure our backend later when we add authentication.
Next
File: src/pages/storage/api-reference.mdx
title: "API Reference for Nhost Storage"
| Endpoint | Method | Description |
|---|---|---|
/storage/o/ |
GET | Get file |
/storage/m/ |
GET | Get file metadata |
/storage/o// |
GET | Get ZIP of all files in folder |
/storage/m// |
GET | Get metadata of all files in folder |
/storage/o// |
POST | Upload file |
/storage/o// |
DELETE | Delete file |
Get file
Get file
nhost-js-sdk
Not needed, just access the file directly
Request
GET /storage/o/
Response
<file>Get file metadata
Get file metadata
nhost-js-sdk
storage.getMetadata(path: string);Request
GET /storage/m/
Response
{
"key": "<path-to-file>",
"AcceptRanges": "bytes",
"LastModified": "2020-01-01T01:02:03.000Z",
"ContentLength": 12345,
"ETag": "Etag",
"ContentType": "<content-type>",
"Metadata": {
"token": "<auto-generated-access-token-uuid>"
}
}Get zip of all files in folder
Get zip of all files in folder.
nhost-js-sdk
Just access the path directly.
Request
GET /storage/o//
Response
Downloadable list.zip fileGet metadata of all files in a folder
Get metadata of all files in a folder.
nhost-js-sdk
storage.getMetadata(path: string);Request
GET /storage/m//
Response
[
{
"key": "<path-to-file>",
"AcceptRanges": "bytes",
"LastModified": "2020-01-01T01:02:03.000Z",
"ContentLength": 12345,
"ETag": "Etag",
"ContentType": "<content-type>",
"Metadata": {
"token": "<auto-generated-access-token-uuid>"
}
},
{
"key": "<other-path-to-file>",
"AcceptRanges": "bytes",
"LastModified": "2020-05-04T03:02:01.000Z",
"ContentLength": 54321,
"ETag": "Etag",
"ContentType": "<content-type>",
"Metadata": {
"token": "<auto-generated-access-token-uuid>"
}
}
]Upload file
Upload file.
nhost-js-sdk
storage.put(path: string, file: File);
```
### Request
`POST /storage/o/`
### Response
```json
{
"key": "<path-to-file>",
"AcceptRanges": "bytes",
"LastModified": "2020-01-01T01:02:03.000Z",
"ContentLength": 12345,
"ETag": "Etag",
"ContentType": "<content-type>",
"Metadata": {
"token": "<auto-generated-access-token-uuid>"
}
}Delete file
Delete file.
nhost-js-sdk
storage.delete(path: string);
```
### Request
`DELETE /storage/o/`
### Response
```bash
204 No ContentFile: src/pages/storage/image-transformation.mdx
title: "Image Transformation"
Transform images on-the-fly using query parameters. Cache headers are sent with the image for browsers to cache the image client-side.
Example
<a
href="https://backend-d32l5h6l.nhost.app/storage/o/public/example.jpg?w=800&q=90"
target="_blank"
https://backend-d32l5h6l.nhost.app/storage/o/public/example.jpg?w=800&q=90
Notice the ?w=800&q=90 at the end of the URL.
Width
Specify width in pixels of the image. If no height (h) is specified, the image automatically calculates the height to keep the image's aspect ratio.
Query parameter: w.
| Min value | 0 |
| Max value | 8192 |
Height
Specify height in pixels of the image. If no width (w) is specified, the image automatically calculates the width to keep the image's aspect ratio.
Query parameter: h.
| Min value | 0 |
| Max height | 8192 |
Quality
Specify the quality of the image.
Query parameter: q.
| Min value | 0 |
| Max value | 100 |
Format
Specify the preferred format of the requested image.
This parameter can be one of the following values: webp, png, jpeg, auto. When the auto value is given the image will be transformed to the webp format if the request header explicitly accepts image/webp, otherwise the default format will be returned.
Query parameter: f.
Rounding
Give the image rounded corners
Query parameter: r.
This parameter can be either an integer or full, if the image has to be fully rounded (e.g. an avatar).
Blur
Add a Gaussian blur to the image.
Query parameter: b.
| Min value | 0.3 |
| Max value | 1000 |
File: src/pages/storage/index.mdx
title: "Nhost Storage"
Nhost Storage lets you handle user-generated files, such as videos and images.
You can define declarative security rules based on the user, file, and other metadata.
All files in the storage are saved in Minio S3, a high-performance object storage with a compatible AWS S3 API.
Access
Storage is accessible with your backend URL under /storage (https://backend-xxx.nhost.app/storage).
After /storage you need to add either /o (object) or /m (metadata).
Object
Object is used to access the actual file. Ex: https://backend-xxx.nhost.app/storage/o/myfile.png.
Metadata
Metadata is used to access metadata about the file. Ex: https://backend-xxx.nhost.app/storage/m/myfile.png.
File: src/pages/storage/security-rules.mdx
title: "Security Rules"
Nhost Storage Security
Rules
All files in Nhost Storage are protected with Security Rules.
With security rules, you can set granular access on who is allowed to do what based on the user and the file.
All storage rules can be by-passed by admins using the Admin Secret. This is done by sending the Admin Secret as a header like:
x-admin-secret: <admin-secret>. The Admin Secret is the same Admin Secret used to login to the Hasura console.
Security rules are written in yaml format.
Here is an example security rules.
functions:
isAuthenticated: "return !!request.auth"
isOwner: "return !!request.auth && userId === request.auth['user-id']"
paths:
/public*:
read: "true"
write: "isAuthenticated()"
/users/:userId/:fileName:
read: "public()"
write: "isOwner(userId)"In the above example we have the following rules:
- Anyone can read files located in the
/public*folder. Because we use a*all paths that starts with/publicwill be covered by this rule. Ex:/public/image.pngand/public/other-path/cv.pdf. - Only logged in users are allowed to write to
/public*. - Anyone can read files from
/users/:userId/:fileName. Ex:/users/1/image.png. - Only logged in users can write to their own directory. Ex user with id
1can upload a file to/users/1/image.pngbut not to/users/2/image.png.
Structure
Storage Rules follows this structure:
functions:
functionName: "one line javascript that returns true or false"
paths:
/path/to/file.png:
operation: "true/false or functionName()"
operation: "true/false or functionName()"
operation: "true/false or functionName()"
/path/to/folder:
operation: "true/false or functionName()"
operation: "true/false or functionName()"
operation: "true/false or functionName()"
/path/to/folder*:
operation: "true/false or functionName()"
operation: "true/false or functionName()"
operation: "true/false or functionName()"Paths
Paths are specified under the paths: directive and specifies that path to the file or folder that you want to set rules for.
Path Variables
You can use path variables in the path to capture values specified at their position in the URL. These path variables can be used in the security rules functions.
Below is an example where we capture :userId as a variable in the URL and use that same userId variable to resolve userId === request.auth['user-id'] in the isOwner function.
functions:
isOwner: "return !!request.auth && userId === request.auth['user-id']"
paths:
/users/:userId/:fileName:
write: "isOwner(userId)"You can not nest paths like this:
paths:
/path/to/:
read: "true/false or functionName()"
/then/:
read: "true/false or functionName()"Functions
You can use a function to resolve if a request should be allowed or not. A function can access information from the user making the request, file metadata and path variables.
A function must return true or false.
Functions are placed after the functions: directive and can be used after an operation like this:
functions:
public: "return true"
paths:
/open-access*:
read: "public()"
write: "public()"Functions can take arguments from the file path. In the example below we'll allow anyone to read a file located in /users/:userId but only users are allowed to write in their own folder with the name of their user id.
functions:
public: "return true"
isOwner: "return !!request.auth && userId === request.auth['user-id']"
paths:
/users/:userId/:fileName:
read: "public()"
write: "isOwner(userId)"Who is allowed to do what given the rules above?
| User id | Path | Operation | Allowed? |
|---|---|---|---|
| 1 | /users/1/image.png |
read (GET) | Yes |
| 1 | /users/2/image.png |
read (GET) | Yes |
| 1 | /users/1/image.png |
write (POST) | Yes |
| 1 | /users/2/image.png |
write (POST)) | No |
| Not logged in | /users/2/image.png |
read (GET)) | Yes |
| Not logged in | /users/1/image.png |
write (POST)) | No |
| Not logged in | /users/2/image.png |
write (POST)) | No |
Available variables
Path variables
Path variables can be accessed in function if they are present in the path (/users/:myVariable) and sent as a parameter to the function isOwner(myVariable).
User
You can access user information at request.auth['']. All variables from the JWT-token claim can be accessed here without the x-hasura. For example, thi is how you access the user's id: request.auth['user-id'].
Operations
The following operations can be used:
createUpload new fileupdateOverwrite existing fileget- Get filelist- List files in directorydelete- Delete file
You can also use helper operations:
writeis the same ascreateandupdateanddelete.readis the same asgetandlist.
Access Token
When a file gets uploaded, Nhost Storage automatically generates a token (access token) for the file. The token will be added to the file's metadata.
Let's say you upload a file to /data/document.txt and get this as return data:
{
"key": "/data/document.txt",
"AcceptRanges": "bytes",
"LastModified": "2020-01-01T01:02:03.000Z",
"ContentLength": 12345,
"ETag": "Etag",
"ContentType": "text/plain",
"Metadata": {
"token": "6a587e56-04d4-415d-bb2c-9a39491e31d2"
}
}And have the following storage rules:
functions:
validToken: 'return request.query.token === resource.Metadata.token'`
paths:
/data*:
read: 'validToken()'Now, given our validToken function, anyone can access /data/document.txt with the token like this: https://backend-xxx.nhost.app/storage/o/data/document.txt?token=6a587e56-04d4-415d-bb2c-9a39491e31d2.
Revoke access tokens
Access tokens can be revoked which means the old access token gets deleted and a new access token created. Only admins with the admin secret (same as Hasura's admin secret) can revoke tokens.
Request to revoke an access token:
Request
POST /storage/m/
Headers
x-admin-secret: <admin-secret>Body
{
"action": "revoke-token"
}Response
{
"key": "<path-to-file>",
"AcceptRanges": "bytes",
"LastModified": "2020-01-01T01:02:03.000Z",
"ContentLength": 12345,
"ETag": "Etag",
"ContentType": "<content-type>",
"Metadata": {
"token": "<new-auto-generated-access-token-uuid>"
}
}