docs Documentation

JavaScript library by @nhost focused on documentation, help. Includes documentation and AI agent context with 23 stars.

23 stars JavaScript JavaScript 52 files · ~128,268 tokens #docs#documentation#help#nhost
RAW Doc

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

Email

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

js
auth.register({ email, password });

With custom user_data, for ex display_name:

js
auth.register({
  email,
  password,
  options: {
    userData: {
      display_name: "Joe Doe",
    },
  },
});

Request

POST /auth/register

json
{
  "email": "[email protected]",
  "password": "password"
}

With custom user_data, for ex display_name:

json
{
  "email": "[email protected]",
  "password": "password",
  "user_data": {
    "display_name": "Elon Musk"
  }
}

Response

json
{
  "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_USERS is false, session will return null.


Login user

Login user

nhost-js-sdk

js
auth.login({ email, password });

Request

POST /auth/login

json
{
  "email": "[email protected]",
  "password": "password"
}

Response

bash
Set-Cookie: refresh_token=...
Set-Cookie: permission_variables=...
json
{
  "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:

json
{
  "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

ts
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

json
{
  "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

ts
auth.activate(ticket: string);

Request

POST /auth/activate?ticket=<ticket>

Response

json
204 No Content

Get 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

bash
Cookie: refresh_token=<current_refresh_token>

Response

bash
Cookie: refresh_token=<new_refresh_token>
json
{
  "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

bash
Cookie: refresh_token=<current_refresh_token>

Response

bash
204 No Content

Delete 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

bash
Set-Cookie: refresh_token=...
Set-Cookie: permission_variables=...

Response

bash
204 No Content

Change password

For users to change their password when they are logged in and remember their previous password.

nhost-js-sdk

ts
auth.changePassword(oldPassword: string, newPassword: string);

Request

POST /auth/change-password

bash
Set-Cookie: refresh_token=...
Set-Cookie: permission_variables=...
json
{
  "old_password": "<old password>",
  "new_password": "<new password>"
}

Response

bash
204 No Content

Request 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

ts
auth.requestPasswordChange(email: string);

Request

POST /auth/change-password/request

json
{
  "email": "<user's email>"
}

Response

bash
204 No Content

This 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

ts
auth.confirmPasswordChange(newPassword: string, ticket: string);

Request

POST /auth/change-password/change

json
{
  "new_password": "<new password>",
  "ticket": "<ticket>"
}

Response

bash
204 No Content

Generate 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

ts
auth.MFAGenerate(code: string, ticket: string);

Request

POST /auth/mfa/generate

Response

json
{
  "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

ts
auth.MFAEnable(code: string);

Request

POST /auth/mfa/enable

json
{
  "code": "123456"
}

Response

bash
204 No Content

Disable 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

json
{
  "code": "code from mfa client"
}

Response

bash
204 No Content

TOTP 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

ts
auth.MFATotp(code: string, ticket: string);

Request

POST /auth/mfa/generate

Response

json
{
  "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.

json
{
  "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:

js
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, usually https://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.

html
<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
  • Google
  • Facebook
  • Linkedin

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:

json
{
  "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:

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:

js
import { auth } from "utils/nhost";

const email = "[email protected]";
const password = "123123";

auth.register({ email, password });

Login user:

js
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:

How to disable MFA for a user

  • Get a code (one-time password) from password manager
  • Use code to disable MFA.

How to login when MFA is enabled

  • Login using email and password
  • Save ticket response from the login
  • Copy code one-time password from password manager
  • Send ticket and code to 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:

json
{
  "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:

bash
x-hasura-default-role: user
x-hasura-allowed-roles: ["user", "me", "editor"]

Then the user can make a request as an editor like this:

jsx
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 public by 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:

html
<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
  • Google
  • Facebook
  • LinkedIn

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 of github, google, facebook, linkedin.


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

bash
$ nhost deploy

✔ migrations deployed

✔ metadata deployed

All 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.

bash
$ 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:4000

The 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

bash
$ docker logs -f nhost_postgres

Hasura GraphQL Engine

bash
$ docker logs -f nhost_hasura

Hasura Backend Plus

bash
$ docker logs -f nhost_hbp

Custom API

bash
$ docker logs -f nhost_api

Configuration

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):

bash:.env.development
JWT_CUSTOM_FIELDS=company_id

Registration 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.

bash:.env.development
REGISTRATION_CUSTOM_FIELDS=display_name

Networking

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:

text
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.

bash
$ nhost down


All Nhost services down

File: src/pages/cli/env-ls.mdx


title: "Nhost env:ls"

nhost env:ls lists all dev environment variables.

bash
$ nhost env:ls
Environment variables for todos-cli.

name                value (dev)
FIRST_ENV           22
ANOTHER_ONE         myAPIkey

File: 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.

bash
$ nhost env:pull
Overwriting existing .env.development file
Downloading development environment variables for project: todos-cli
✅  .env.development file updated

File: src/pages/cli/index.mdx


title: "Nhost CLI (beta)"

The CLI is your primary interface when doing local development.

Install

bash
$ npm install -g nhost

Dependencies

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

bash
$ hasura version

Update your Hasura CLI:

bash
$ 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:

bash
$ 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

bash
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 account

Workflow

bash
# 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 dev

Beta

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 init does not yet work on Windows. Follow the GitHub issue.

bash
$ nhost init
? Select Project (Use arrow keys)
  ---personal---
❯ todos-cli

# Enter to choose the project to initialize

⠇ Initializing todos-cli
Nhost project successfully initialized

Let's now look at some of the configuration created.

bash
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 files

The 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.

bash
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.development

Also 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.



nhost link links an existing directory with an existing Nhost project. Usually after you've cloned a git repository.

bash
$ nhost link
? Select Project (Use arrow keys)
  ---personal---
❯ todos-cli
Project linked: todos-cli

Once 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.

bash
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:

  1. Init your project with the nhost-cli: nhost init
  2. Connect your repository to your Nhost project under Settings -> Git.
  3. 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

js:api/index.js
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 api to .vercelignore so the API-folder does not conflict with Vercel's deployment.

Example paths and endpoints

Given this folder and file structure:

bash
|____api
| |____index.js
| |____users
| | |____index.js
| | |____active.js
| |____my-company.js

The following endpoints will be available:

bash
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:

bash
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.

js:api/index.ts
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.

GraphQL.org


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.

graphql:mutation
mutation InsertTodo {
  insert_todos(
    objects: [
      {
        title: "Delete Firebase account"
        body: "Migrate to nhost.io"
        done: false
      }
    ]
  ) {
    returning {
      id
      title
      body
      done
    }
  }
}
json:response
{
  "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.

graphql:mutation
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:

graphql:mutation
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_conflict key 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:

graphql:mutation
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:

graphql:mutation
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:

graphql:mutation
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:

graphql:mutation
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.

graphql:query
query GetTodos {
  todos {
    title
    body
    done
  }
}
json:response
{
  "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.

graphql:query
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)
      }
    }
  }
}
json:response
{
  "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:

graphql{1}
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!.

graphql{1-2}:mutation
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.

js
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.

js
if (req.headers["x-webhook-secret"] !== process.env.NHOST_WEBHOOK_SECRET) {
  return send(res, 401, "incorrect webhook secret");
}
// continue with your webhook

File: 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:

bash
x-hasura-user-id
x-hasura-allowed-roles
x-hasura-default-role

The 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.

  1. Use the role user and click on the Insert column.
  2. Select Without any checks.
  3. Select the columns you want the user to insert. Here we'll only select name since the other columns will be filled in with default values and we don't want users to override them.
  4. For column presets we select user_id to be the value of x-hasura-user-id (the user's id). This way, we make sure that the user_id is 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.

  1. Use the role user and click on the Select column.
  2. 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.
  3. Limit number of rows to 100 (or some other relevant number).
  4. 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:

graphql
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:

  1. Creates the table in PostgreSQL.
  2. Hasura tracks the table and automatically generates a GraphQL API.

You can also create tables directly in PostgreSQL.

How to create a table

  1. In the Hasura Console. Click on the Add table in the left menu.
  2. Choose a Table name.
  3. Add your Columns.
  4. Add a Primary Key. Usually this is your id column.
  5. (Optional) Add Foreign Keys if any.
  6. (Optional) Add Unique Keys.
  7. (Optional) Add a comment.
  8. 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

  1. Click on the table you want to edit in the left menu.
  2. Click on Modify.
  3. Modify the table column.

Delete table

  1. Click on the table you want to delete in the left menu.
  2. Click on Modify.
  3. Scroll to the bottom and click on Delete table.
  4. 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.

  1. Click on Data in the top menu.
  2. A list of untracked tables is presented.
  3. Click Track All (recommended) or click Track for each table you want to track.

Track foreign-key relations

  1. Click on Data in the top menu.
  2. A list of untracked foreign-key relations is presented.
  3. 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

bash
$ npm install nhost-js-sdk

or

bash
$ yarn add nhost-js-sdk

Setup

js:/src/utils/nhost
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

js
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.

js
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.

js
auth.login({ email, password });

// OAuth provider: google | github | linkedin ...
auth.login({ provider })

Users can logout via logout().

js
auth.logout();

To register a callback to run when the auth state changes (e.g., when a user logs out) is done via onAuthStateChanged().

js
auth.onAuthStateChanged((loggedIn) => {
  console.log("auth state changed!");
  console.log({ loggedIn });
});

To check if a user is logged in or not use isAuthenticated().

js
auth.isAuthenticated();

notes:

  • true means the user is logged in
  • false means the user is not logged in
  • null means that a request to login the user is ongoing.

For getting a user session's JWT Token use getJWTToken().

js
auth.getJWTToken();

And for its claims getClaim().

js
auth.getClaim("x-hasura-user-id");

To activate a user account use activate().

js
auth.activate(<ticket>);

For changing an account's email address without confirmation use changeEmail(). The user must be logged in.

js
auth.changeEmail(new_email);

Or with a confirmation email requestEmailChange().

js
auth.requestEmailChange(new_email);

To confirm the email change use confirmEmailChange() and pass in the ticket received with the email.

js
auth.confirmEmailChange(ticket);

For changing a user's password use changePassword().

js
auth.changePassword(oldPassword, newPassword);

Or with a confirmation email use requestPasswordChange().

js
auth.requestPasswordChange(email);

To confirm the password change use confirmPasswordChange().

js
auth.confirmPasswordChange(new_password, ticket);

To generate a MFA QR-code use MFAGenerate(). The user must be logged in.

js
auth.MFAGenerate();

Enabling MFA is done via enable().

js
auth.enable(code);

And disabling is done via disable().

js
auth.disable(code);

To login using MFA TOTP use MFATotp() and pass in the ticket returned by login() (when MFA is enabled).

js
auth.MFATotp(code, ticket);

Storage

Uploading a file is done using put().

js
storage.put(path, file, metadata?, onUploadProgress?);

To delete a file use delete().

js
storage.delete(path);

To get the metadata for a file use getMetadata().

js
storage.getMetadata(path);

Other environments

React Native

Install:

bash
$ yarn add @react-native-community/async-storage

Installation instructions can be found at https://react-native-community.github.io/async-storage/docs/install.

js
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

js
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:

bash
$ expo install expo-secure-store

More info: https://docs.expo.io/versions/latest/sdk/securestore/.

js
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:

bash
$ npm install --save @nhost/nuxt

or using Yarn:

bash
$ yarn add @nhost/nuxt

Other recommended NPM packages: nhost-js-sdk @nuxtjs/apollo graphql-tag

Add the module and configure nhost in nuxt.config.js:

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.

  1. Add nhost/auth to your Nuxt config middleware:
js
{
  router: {
    middleware: ["nhost/auth"];
  }
}
  1. Define the home and logout routes on the nhost config
js
{
  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.

  1. Finally, add an nhostAuth property to your Nuxt pages:
js
export default {
  nhostAuth: true,
};

nhostAuth takes the following values:

  1. true: Users must be authenticated to access this page. If the user is not authenticated, the user gets redirected to the logout route.
  2. false: This is the default value; no authentication required.
  3. 'guest': This page can only be accessed by unauthenticated users. Users who are logged in will be redirected to the home route.

Typescript

If you're using Typescript, make sure to include @nhost/nuxt to your Typescript config types:

js
{
  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:


js:plugins/nuxt-apollo-config.js
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;
    },
  };
};
js:nhost-apollo-ws-client.js
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:

json:nuxt.config.js
{
  [...]
  "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

bash
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

js: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 };

Read more about nhost-js-sdk here.

Create React App

See full Nhost Create React App with Apollo.

jsx:src/index.js
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.

jsx:pages/_app.js
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.

jsx
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

bash
npm install @nhost/[email protected]

Create React App

Setup

See full Nhost Create React App with Apollo.

jsx:src/index.js
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.

jsx:src/components/auth-gate.js
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.

jsx{7,19,23}:src/index.js
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

jsx:pages/_app.js
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.

jsx:components/private-route.jsx
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

jsx:pages/index.js
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.users
  • auth.account_providers
  • auth.account_roles
  • auth.accounts
  • auth.providers
  • auth.refresh_tokens
  • auth.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-sdk is an open-source JavaScript library for interacting with Nhost's auth and storage.

bash
$ npm install nhost-js-sdk

Now, we need to configure and initialize a connection to Nhost. Create a new folder, src/utils, and create nhost.js inside it.

js: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 };

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:

bash
$ 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.

jsx{5,6,10,11,13,14}:src/index.js
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:

jsx:src/components/login.js
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.

jsx{4,5,14-19,21-23}:src/index.js
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.

jsx{3,4,21,22}:src/app.js
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 public role. Only logged-in users can insert and select todos.
  • Add user_id to the todos table 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.

jsx:src/components/auth-gate.js
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.

jsx{5,20,24}:src/index.js
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_id refers 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 user role.

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:

bash
$ npx create-react-app nhost-todos

Once 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.

bash
$ cd nhost-todos
$ npm install @nhost/[email protected] @apollo/client graphql react-router-dom

Create a jsconfig.json file in the root of your project to make imports easier.

json:jsconfig.json
{
  "compilerOptions": {
    "baseUrl": "src"
  },
  "include": ["src"]
}

Start the app.

bash
$ npm start

Add 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/graphql with your project's GraphQL API endpoint. You can find the endpoint in your project's dashboard.

jsx{4,8,10}:src/index.js
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:

jsx:src/app.js
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.

jsx{1,2,6,19-43}:src/app.js
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.

jsx{2,4,9}:src/app.js
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

Authentication


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-apollo and @nhost/react-auth are 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

Nhost Backend


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

Todos Table


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.

graphql
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 public role 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

Frontend Web App


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

bash
<file>

Get file metadata

Get file metadata

nhost-js-sdk

ts
storage.getMetadata(path: string);

Request

GET /storage/m/

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>"
  }
}

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

bash
Downloadable list.zip file

Get metadata of all files in a folder

Get metadata of all files in a folder.

nhost-js-sdk

ts
storage.getMetadata(path: string);

Request

GET /storage/m//

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>"
    }
  },
  {
    "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

ts
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

ts
storage.delete(path: string);
```

### Request

`DELETE /storage/o/`

### Response

```bash
204 No Content

File: 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.

yaml
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 /public will be covered by this rule. Ex: /public/image.png and /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 1 can upload a file to /users/1/image.png but not to /users/2/image.png.

Structure

Storage Rules follows this structure:

yaml
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.

yaml
functions:
  isOwner: "return !!request.auth && userId === request.auth['user-id']"
paths:
  /users/:userId/:fileName:
    write: "isOwner(userId)"

You can not nest paths like this:

yaml
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:

yaml
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.

yaml
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:

  • create Upload new file
  • update Overwrite existing file
  • get - Get file
  • list - List files in directory
  • delete - Delete file

You can also use helper operations:

  • write is the same as create and update and delete.
  • read is the same as get and list.

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:

json
{
  "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:

yaml
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

json
x-admin-secret: <admin-secret>

Body

json
{
  "action": "revoke-token"
}

Response

json
{
  "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>"
  }
}