Compare commits

..

61 Commits

Author SHA1 Message Date
github-actions[bot]
e4341c3706 chore: update versions (#2462)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/dashboard@1.5.0

### Minor Changes

-   c2ef17c0a: feat: add support for new Team plan

## @nhost/docs@2.1.0

### Minor Changes

-   65b6a48d5: feat: added graphite/cli documentation

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-01-22 11:31:49 -01:00
Nuno Pato
c2ef17c0a0 feat: dashboard: new Team plan (#2473) 2024-01-22 11:13:26 -01:00
David Barroso
1045ea0a46 fix(docs): set correct hostname for connecting to run service internally (#2471) 2024-01-17 12:34:01 +01:00
Nevada Le Master
5faaf36e26 chore: add maskedErrors param to CreateServerProps (#2129)
this PR addresses https://github.com/nhost/nhost/issues/1218, adding
`maskedErrors` param when creating Stripe and Google Translate GraphQL
Yoga servers

Co-authored-by: David Barroso <dbarrosop@dravetech.com>
2024-01-16 16:36:51 +01:00
David Barroso
65b6a48d51 feat(docs): added graphite/cli documentation (#2457)
Co-authored-by: Nuno Pato <nunopato@gmail.com>
2024-01-10 17:19:58 +01:00
github-actions[bot]
23e18fb734 chore: update versions (#2454)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/docs@2.0.0

### Major Changes

-   6d08b3430: New Docs powered by Mintlify

## @nhost/dashboard@1.4.0

### Minor Changes

-   7883bbcbd: feat: don't show deprecated plans
- 44be6dc0a: feat: set redirectTo during sign-in to support preview
environments

### Patch Changes

- 3c3594898: fix: allow access to graphite when configured running in
local dashboard
-   32c246b7a: chore: update docs icon

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-01-10 14:59:31 +01:00
David Barroso
44be6dc0a5 feat (dashboard): set redirectTo during sign-in with twitter to support preview environments (#2461)
Co-authored-by: Hassan Ben Jobrane <hsanbenjobrane@gmail.com>
2024-01-10 14:48:58 +01:00
Hassan Ben Jobrane
3c35948986 fix: refactor plan check when accessing ai services (#2456)
fixes https://github.com/nhost/nhost/issues/2455
2024-01-10 12:47:34 +01:00
David Barroso
7883bbcbd1 feat (dashboard): don't show deprecated plans (#2458) 2024-01-09 16:36:27 +01:00
Nuno Pato
42fddcf790 Merge pull request #2425 from nhost/docs/new-version
chore(docs): new docs
2024-01-09 14:29:15 -01:00
Nuno Pato
5d1a444451 asd 2024-01-09 14:03:36 -01:00
Nuno Pato
98d17a3066 asd 2024-01-09 12:42:14 -01:00
Nuno Pato
f70f36be08 asd 2024-01-09 11:06:13 -01:00
Nuno Pato
50fe08624f Merge branch 'main' into docs/new-version 2024-01-09 10:16:04 -01:00
Nuno Pato
6cb7dd8203 asd 2024-01-08 23:45:49 -01:00
Hassan Ben Jobrane
f859159ef5 Merge pull request #2453 from nhost/chore/update-docs-icon
chore(dashboard): update docs icon
2024-01-08 15:14:37 +01:00
Hassan Ben Jobrane
32c246b7a9 chore: add changeset 2024-01-08 11:24:35 +01:00
Hassan Ben Jobrane
f004fd067a chore: update docs icons 2024-01-08 11:24:03 +01:00
David Barroso
82340b5d54 remove migration step from ai guide (#2450) 2024-01-06 13:05:41 +01:00
Nuno Pato
8fff3e06bd asd 2024-01-05 14:54:40 -01:00
Nuno Pato
527a661222 asd 2024-01-05 14:47:46 -01:00
Nestor Manrique
93f573ea98 Merge pull request #2443 from nhost/feat/docs-for-run-services-health-checks
feat(docs): for run services health checks
2024-01-03 10:51:09 +01:00
Nestor Manrique
ac78629414 Remove vscode settings json 2024-01-03 10:23:47 +01:00
Nestor Manrique
3403744c22 wip 2024-01-03 10:09:02 +01:00
Nuno Pato
ddadf3399c Merge pull request #2441 from nhost/feat/docs-for-postgres-wal-settings
feat(docs): add postgres wal settings to docs
2024-01-02 18:52:11 -01:00
Nestor Manrique
c768341ce8 Adjust PR comments and run sections on product page 2024-01-02 16:34:49 +01:00
Nestor Manrique
1396cbe4c0 Add more details to healthcheck docs 2024-01-02 16:05:31 +01:00
Nestor Manrique
76761b4970 Add docs for run health checks config 2024-01-02 16:00:40 +01:00
Nestor Manrique
0bc9a41e51 Add postgres wal settings to docs 2024-01-02 14:55:26 +01:00
Nuno Pato
7107089a29 asd 2023-12-24 11:10:50 -01:00
David Barroso
a6c7300e14 asd 2023-12-22 16:34:25 +01:00
Nuno Pato
1a84610b74 asd 2023-12-22 14:34:00 -01:00
Nuno Pato
6c43529eff asd 2023-12-22 13:35:45 -01:00
Nuno Pato
63309cbcd6 asd 2023-12-22 13:24:47 -01:00
David Barroso
998b1d5963 asd 2023-12-22 15:21:08 +01:00
David Barroso
42d2a89de3 asd 2023-12-22 15:17:34 +01:00
David Barroso
731f094cf8 asd 2023-12-22 15:17:34 +01:00
David Barroso
3454605582 asd 2023-12-22 15:17:34 +01:00
David Barroso
e4479afab4 asd 2023-12-22 15:17:34 +01:00
David Barroso
6edae34bf0 asd 2023-12-22 15:17:34 +01:00
David Barroso
80b6464f60 asd 2023-12-22 15:17:34 +01:00
David Barroso
e3880dbe8a asd 2023-12-22 15:17:33 +01:00
David Barroso
ea991228e2 asd 2023-12-22 15:17:33 +01:00
Nuno Pato
7cb568be52 asd 2023-12-22 15:17:33 +01:00
Nuno Pato
dacaa7cad7 ads 2023-12-22 15:17:33 +01:00
Nuno Pato
30a688778e asd 2023-12-22 15:17:33 +01:00
Nuno Pato
d4f79c05b4 asd 2023-12-22 15:17:33 +01:00
Nuno Pato
e10d313e37 asd 2023-12-22 15:17:33 +01:00
Nuno Pato
77e8fb471c asd 2023-12-22 15:17:33 +01:00
Nuno Pato
f40a3f23ac asd 2023-12-22 15:17:33 +01:00
Nuno Pato
17dea7e60b asd 2023-12-22 15:17:33 +01:00
Nuno Pato
23527fc388 asd 2023-12-22 15:17:33 +01:00
Nuno Pato
8ec6b85bac asd 2023-12-22 15:17:33 +01:00
Nuno Pato
b067838984 asd 2023-12-22 15:17:33 +01:00
Nuno Pato
7553506e18 asd 2023-12-22 15:17:33 +01:00
Nuno Pato
e58a9f1aaa asd 2023-12-22 15:17:33 +01:00
Nuno Pato
d4bfea963f asd 2023-12-22 15:17:33 +01:00
Nuno Pato
88779ad950 asd 2023-12-22 15:17:33 +01:00
Nuno Pato
90929e9357 asd 2023-12-22 15:17:33 +01:00
David Barroso
2f4d5814ed asd 2023-12-22 15:17:33 +01:00
Nuno Pato
6d08b34309 new docs 2023-12-22 15:17:32 +01:00
942 changed files with 30174 additions and 15183 deletions

22
.github/CODEOWNERS vendored
View File

@@ -1,14 +1,14 @@
# Documentation
# https://help.github.com/en/articles/about-code-owners
/packages @szilarddoro
/packages/docgen @szilarddoro
/integrations/stripe-graphql-js @elitan
/.github @szilarddoro
/dashboard/ @szilarddoro
/docs/ @elitan
/config/ @szilarddoro
/examples/ @szilarddoro
/examples/codegen-react-apollo @elitan @szilarddoro
/examples/codegen-react-query @elitan @szilarddoro
/examples/react-apollo-crm @elitan @szilarddoro
/packages @nunopato @onehassan
/packages/docgen @nunopato @onehassan
/integrations/stripe-graphql-js @nunopato @onehassan
/.github @nunopato @onehassan
/dashboard/ @nunopato @onehassan
/docs/ @nunopato @onehassan
/config/ @nunopato @onehassan
/examples/ @nunopato @onehassan
/examples/codegen-react-apollo @nunopato @onehassan
/examples/codegen-react-query @nunopato @onehassan
/examples/react-apollo-crm @nunopato @onehassan

1
.github/labeler.yml vendored
View File

@@ -4,7 +4,6 @@ dashboard:
documentation:
- any:
- docs/**/*
- '!docs/docs/reference/docgen/**/*'
examples:
- examples/**/*

2
.gitignore vendored
View File

@@ -23,7 +23,7 @@ node_modules/
tmp/
.pnpm-store
.turbo
.env
.env*
.secrets
out/

View File

@@ -1,7 +0,0 @@
{
"editor.codeActionsOnSave": {
"source.organizeImports": true
},
"eslint.workingDirectories": ["./dashboard"],
"typescript.tsdk": "node_modules/typescript/lib"
}

View File

@@ -1,5 +1,23 @@
# @nhost/dashboard
## 1.5.0
### Minor Changes
- c2ef17c0a: feat: add support for new Team plan
## 1.4.0
### Minor Changes
- 7883bbcbd: feat: don't show deprecated plans
- 44be6dc0a: feat: set redirectTo during sign-in to support preview environments
### Patch Changes
- 3c3594898: fix: allow access to graphite when configured running in local dashboard
- 32c246b7a: chore: update docs icon
## 1.3.2
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/dashboard",
"version": "1.3.2",
"version": "1.5.0",
"private": true,
"scripts": {
"preinstall": "npx only-allow pnpm",

View File

@@ -0,0 +1,7 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 6H10" stroke="#21324B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 8H10" stroke="#21324B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 10H8" stroke="#21324B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.79289 13.5H3C2.86739 13.5 2.74021 13.4473 2.64645 13.3536C2.55268 13.2598 2.5 13.1326 2.5 13V3C2.5 2.86739 2.55268 2.74021 2.64645 2.64645C2.74021 2.55268 2.86739 2.5 3 2.5H13C13.1326 2.5 13.2598 2.55268 13.3536 2.64645C13.4473 2.74021 13.5 2.86739 13.5 3V9.79289C13.5 9.85855 13.4871 9.92357 13.4619 9.98423C13.4368 10.0449 13.4 10.1 13.3536 10.1464L10.1464 13.3536C10.1 13.4 10.0449 13.4368 9.98423 13.4619C9.92357 13.4871 9.85855 13.5 9.79289 13.5V13.5Z" stroke="#21324B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.4548 9.99948H10V13.4545" stroke="#21324B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,7 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 6H10" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 8H10" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 10H8" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.79289 13.5H3C2.86739 13.5 2.74021 13.4473 2.64645 13.3536C2.55268 13.2598 2.5 13.1326 2.5 13V3C2.5 2.86739 2.55268 2.74021 2.64645 2.64645C2.74021 2.55268 2.86739 2.5 3 2.5H13C13.1326 2.5 13.2598 2.55268 13.3536 2.64645C13.4473 2.74021 13.5 2.86739 13.5 3V9.79289C13.5 9.85855 13.4871 9.92357 13.4619 9.98423C13.4368 10.0449 13.4 10.1 13.3536 10.1464L10.1464 13.3536C10.1 13.4 10.0449 13.4368 9.98423 13.4619C9.92357 13.4871 9.85855 13.5 9.79289 13.5V13.5Z" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.4548 9.99948H10V13.4545" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -4,9 +4,17 @@ import type { DetailedHTMLProps, HTMLProps } from 'react';
import { twMerge } from 'tailwind-merge';
export interface ContactUsProps
extends DetailedHTMLProps<HTMLProps<HTMLDivElement>, HTMLDivElement> {}
extends DetailedHTMLProps<HTMLProps<HTMLDivElement>, HTMLDivElement> {
isTeam?: boolean;
isOwner?: boolean;
}
export default function FeedbackForm({ className, ...props }: ContactUsProps) {
export default function FeedbackForm({
className,
isTeam,
isOwner,
...props
}: ContactUsProps) {
return (
<div
className={twMerge(
@@ -19,6 +27,30 @@ export default function FeedbackForm({ className, ...props }: ContactUsProps) {
Contact us
</Text>
{isTeam && isOwner && (
<Text>
If this is a new Team project, or you need to manage members, reach
out to us on discord or via email at{' '}
<Link
href="mailto:support@nhost.io"
target="_blank"
rel="noopener noreferrer"
underline="hover"
>
support@nhost.io
</Link>{' '}
so we can have your dedicated channel set up.
</Text>
)}
{isTeam && !isOwner && (
<Text>
As part of a team plan you can reach out to us on the private channel
for this workspace. If you haven&apos;t been added to the channel, ask
the workspace owner to add you.
</Text>
)}
<Text>
To report issues with Nhost, please open a GitHub issue in the{' '}
<Link

View File

@@ -13,6 +13,7 @@ import { Dropdown } from '@/components/ui/v2/Dropdown';
import { GraphiteIcon } from '@/components/ui/v2/icons/GraphiteIcon';
import { DevAssistant } from '@/features/ai/DevAssistant';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsCurrentUserOwner } from '@/features/projects/common/hooks/useIsCurrentUserOwner';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { ApplicationStatus } from '@/types/application';
import { getToastStyleProps } from '@/utils/constants/settings';
@@ -37,6 +38,8 @@ export default function Header({ className, ...props }: HeaderProps) {
const { currentProject, refetch: refetchProject } =
useCurrentWorkspaceAndProject();
const isOwner = useIsCurrentUserOwner();
const isProjectUpdating =
currentProject?.appStates[0]?.stateId === ApplicationStatus.Updating;
@@ -114,7 +117,11 @@ export default function Header({ className, ...props }: HeaderProps) {
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
>
<ContactUs className="max-w-md" />
<ContactUs
className="max-w-md"
isTeam={currentProject?.plan?.name === 'Team'}
isOwner={isOwner}
/>
</Dropdown.Content>
</Dropdown.Root>
)}

View File

@@ -15,6 +15,8 @@ import {
} from '@/features/ai/DevAssistant/state';
import { useAdminApolloClient } from '@/features/projects/common/hooks/useAdminApolloClient';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsGraphiteEnabled } from '@/features/projects/common/hooks/useIsGraphiteEnabled';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { getToastStyleProps } from '@/utils/constants/settings';
import {
useSendDevMessageMutation,
@@ -33,6 +35,7 @@ export type Message = Omit<
>;
export default function DevAssistant() {
const isPlatform = useIsPlatform();
const { currentProject, currentWorkspace } = useCurrentWorkspaceAndProject();
const [loading, setLoading] = useState(false);
@@ -45,6 +48,8 @@ export default function DevAssistant() {
const [startDevSession] = useStartDevSessionMutation({ client: adminClient });
const [sendDevMessage] = useSendDevMessageMutation({ client: adminClient });
const { isGraphiteEnabled } = useIsGraphiteEnabled();
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
@@ -141,7 +146,7 @@ export default function DevAssistant() {
}
};
if (currentProject.plan.isFree) {
if (isPlatform && currentProject?.plan?.isFree) {
return (
<Box className="p-4">
<UpgradeToProBanner
@@ -157,7 +162,12 @@ export default function DevAssistant() {
);
}
if (!currentProject.plan.isFree && !currentProject.config?.ai) {
if (
(isPlatform &&
!currentProject?.plan?.isFree &&
!currentProject.config?.ai) ||
!isGraphiteEnabled
) {
return (
<Box className="p-4">
<Alert className="grid w-full grid-flow-col place-content-between items-center gap-2">

View File

@@ -107,7 +107,7 @@ export default function WorkspaceSidebar({
<div className="grid grid-flow-row gap-2">
<Resource
text="Documentation"
logo="Question"
logo="Note"
link="https://docs.nhost.io"
/>
<Resource

View File

@@ -2,6 +2,7 @@ import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import { getHasuraAdminSecret } from '@/utils/env';
import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client';
import { useMemo } from 'react';
export default function useAdminApolloClient() {
const { currentProject } = useCurrentWorkspaceAndProject();
@@ -12,18 +13,24 @@ export default function useAdminApolloClient() {
'graphql',
);
const adminClient = new ApolloClient({
cache: new InMemoryCache(),
link: new HttpLink({
uri: serviceUrl,
headers: {
'x-hasura-admin-secret':
process.env.NEXT_PUBLIC_ENV === 'dev'
? getHasuraAdminSecret()
: currentProject?.config?.hasura.adminSecret,
},
}),
});
const projectAdminSecret = currentProject.config?.hasura?.adminSecret;
const adminClient = useMemo(
() =>
new ApolloClient({
cache: new InMemoryCache(),
link: new HttpLink({
uri: serviceUrl,
headers: {
'x-hasura-admin-secret':
process.env.NEXT_PUBLIC_ENV === 'dev'
? getHasuraAdminSecret()
: projectAdminSecret,
},
}),
}),
[serviceUrl, projectAdminSecret],
);
return {
adminClient,

View File

@@ -10,7 +10,7 @@ export default function useIsCurrentUserOwner() {
const { currentWorkspace, loading } = useCurrentWorkspaceAndProject();
const currentUser = useUserData();
if (loading || !currentWorkspace.workspaceMembers || !currentUser) {
if (loading || !currentWorkspace?.workspaceMembers || !currentUser) {
return false;
}

View File

@@ -0,0 +1 @@
export { default as useIsGraphiteEnabled } from './useIsGraphiteEnabled';

View File

@@ -0,0 +1,14 @@
import { useAdminApolloClient } from '@/features/projects/common/hooks/useAdminApolloClient';
import { useGetGraphiteSessionsQuery } from '@/utils/__generated__/graphite.graphql';
export default function useIsGraphiteEnabled() {
const { adminClient } = useAdminApolloClient();
const { error } = useGetGraphiteSessionsQuery({
client: adminClient,
});
return {
isGraphiteEnabled: !error,
};
}

View File

@@ -1,6 +1,7 @@
const planDescriptions = {
Starter: '1 GB database, 5 GB of file storage, 10 GB of network traffic.',
Pro: '10 GB database, 25 GB of file storage, 50 GB of network traffic, and backups.',
Team: 'Reach out to us at support@nhost.io to have your private channel set up.',
};
export default planDescriptions;

View File

@@ -16,7 +16,7 @@ export default function OverviewTopBar() {
const isPlatform = useIsPlatform();
const { currentWorkspace, currentProject } = useCurrentWorkspaceAndProject();
const isOwner = useIsCurrentUserOwner();
const isPro = !currentProject?.plan?.isFree;
const isStarter = currentProject?.plan?.name === 'Starter';
const { openDialog } = useDialog();
const { maintenanceActive } = useUI();
@@ -65,7 +65,6 @@ export default function OverviewTopBar() {
>
{currentProject.name}
</Text>
{currentProject.creator && (
<Text
color="secondary"
@@ -81,15 +80,14 @@ export default function OverviewTopBar() {
ago
</Text>
)}
<div className="mt-1 inline-grid grid-flow-col items-center justify-start gap-2 md:mt-0">
<Chip
size="small"
label={isPro ? 'Pro' : 'Starter'}
color={isPro ? 'primary' : 'default'}
label={currentProject.plan.name}
color={!isStarter ? 'primary' : 'default'}
/>
{!isPro && isOwner && (
{isStarter && isOwner && (
<Button
variant="borderless"
className="mr-2"

View File

@@ -80,7 +80,7 @@ function AllWorkspaceApps() {
<Chip
size="small"
label={project.plan.isFree ? 'Starter' : 'Pro'}
label={project.plan.name}
color={project.plan.isFree ? 'default' : 'primary'}
/>
</ListItem.Button>

View File

@@ -32,7 +32,7 @@ query PrefetchNewApp {
regions(order_by: { city: asc }) {
...PrefetchNewAppRegions
}
plans(order_by: { sort: asc }) {
plans(order_by: {sort: asc}, where: {deprecated: {_eq: false}}) {
...PrefetchNewAppPlans
}
workspaces {

View File

@@ -0,0 +1,7 @@
query getGraphiteSessions {
graphite {
sessions {
sessionID
}
}
}

View File

@@ -9,14 +9,14 @@ import { Link } from '@/components/ui/v2/Link';
import { Text } from '@/components/ui/v2/Text';
import { AssistantForm } from '@/features/ai/AssistantForm';
import { AssistantsList } from '@/features/ai/AssistantsList';
import { useAdminApolloClient } from '@/features/projects/common/hooks/useAdminApolloClient';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import { getHasuraAdminSecret } from '@/utils/env';
import { useIsGraphiteEnabled } from '@/features/projects/common/hooks/useIsGraphiteEnabled';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
useGetAssistantsQuery,
type GetAssistantsQuery,
} from '@/utils/__generated__/graphite.graphql';
import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client';
import { useMemo, type ReactElement } from 'react';
export type Assistant = Omit<
@@ -26,34 +26,15 @@ export type Assistant = Omit<
export default function AssistantsPage() {
const { openDrawer } = useDialog();
const isPlatform = useIsPlatform();
const { currentWorkspace, currentProject } = useCurrentWorkspaceAndProject();
const adminSecret = currentProject?.config?.hasura?.adminSecret;
const { adminClient } = useAdminApolloClient();
const { isGraphiteEnabled } = useIsGraphiteEnabled();
const serviceUrl = generateAppServiceUrl(
currentProject?.subdomain,
currentProject?.region,
'graphql',
);
const client = useMemo(
() =>
new ApolloClient({
cache: new InMemoryCache(),
link: new HttpLink({
uri: serviceUrl,
headers: {
'x-hasura-admin-secret':
process.env.NEXT_PUBLIC_ENV === 'dev'
? getHasuraAdminSecret()
: adminSecret,
},
}),
}),
[serviceUrl, adminSecret],
);
const { data, loading, refetch } = useGetAssistantsQuery({ client });
const { data, loading, refetch } = useGetAssistantsQuery({
client: adminClient,
});
const assistants = useMemo(() => data?.graphite?.assistants || [], [data]);
@@ -64,7 +45,7 @@ export default function AssistantsPage() {
});
};
if (currentProject.plan.isFree) {
if (isPlatform && currentProject?.plan?.isFree) {
return (
<Box className="p-4" sx={{ backgroundColor: 'background.default' }}>
<UpgradeToProBanner
@@ -80,7 +61,12 @@ export default function AssistantsPage() {
);
}
if (!currentProject.plan.isFree && !currentProject.config?.ai) {
if (
(isPlatform &&
!currentProject?.plan?.isFree &&
!currentProject.config?.ai) ||
!isGraphiteEnabled
) {
return (
<Box className="p-4" sx={{ backgroundColor: 'background.default' }}>
<Alert className="grid w-full grid-flow-col place-content-between items-center gap-2">

View File

@@ -11,14 +11,14 @@ import { Link } from '@/components/ui/v2/Link';
import { Text } from '@/components/ui/v2/Text';
import { AutoEmbeddingsForm } from '@/features/ai/AutoEmbeddingsForm';
import { AutoEmbeddingsList } from '@/features/ai/AutoEmbeddingsList';
import { useAdminApolloClient } from '@/features/projects/common/hooks/useAdminApolloClient';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import { getHasuraAdminSecret } from '@/utils/env';
import { useIsGraphiteEnabled } from '@/features/projects/common/hooks/useIsGraphiteEnabled';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
useGetGraphiteAutoEmbeddingsConfigurationsQuery,
type GetGraphiteAutoEmbeddingsConfigurationsQuery,
} from '@/utils/__generated__/graphite.graphql';
import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client';
import { useRouter } from 'next/router';
import { useEffect, useMemo, useRef, useState, type ReactElement } from 'react';
@@ -32,43 +32,22 @@ export default function AutoEmbeddingsPage() {
const router = useRouter();
const { openDrawer } = useDialog();
const isPlatform = useIsPlatform();
const { currentWorkspace, currentProject } = useCurrentWorkspaceAndProject();
const adminSecret = currentProject?.config?.hasura?.adminSecret;
const serviceUrl = generateAppServiceUrl(
currentProject?.subdomain,
currentProject?.region,
'graphql',
);
const client = useMemo(
() =>
new ApolloClient({
cache: new InMemoryCache(),
link: new HttpLink({
uri: serviceUrl,
headers: {
'x-hasura-admin-secret':
process.env.NEXT_PUBLIC_ENV === 'dev'
? getHasuraAdminSecret()
: adminSecret,
},
}),
}),
[serviceUrl, adminSecret],
);
const { adminClient } = useAdminApolloClient();
const { isGraphiteEnabled } = useIsGraphiteEnabled();
const [currentPage, setCurrentPage] = useState(
parseInt(router.query.page as string, 10) || 1,
);
const [nrOfPages, setNrOfPages] = useState(0);
const offset = useMemo(() => currentPage - 1, [currentPage]);
const { data, loading, refetch } =
useGetGraphiteAutoEmbeddingsConfigurationsQuery({
client,
client: adminClient,
variables: {
limit: limit.current,
offset,
@@ -102,7 +81,7 @@ export default function AutoEmbeddingsPage() {
});
};
if (currentProject.plan.isFree) {
if (isPlatform && currentProject?.plan?.isFree) {
return (
<Box className="p-4" sx={{ backgroundColor: 'background.default' }}>
<UpgradeToProBanner
@@ -118,7 +97,12 @@ export default function AutoEmbeddingsPage() {
);
}
if (!currentProject.plan.isFree && !currentProject.config?.ai) {
if (
(isPlatform &&
!currentProject?.plan?.isFree &&
!currentProject.config?.ai) ||
!isGraphiteEnabled
) {
return (
<Box className="p-4" sx={{ backgroundColor: 'background.default' }}>
<Alert className="grid w-full grid-flow-col place-content-between items-center gap-2">

View File

@@ -34,6 +34,7 @@ import {
import type { ApolloError } from '@apollo/client';
import { useUserData } from '@nhost/nextjs';
import Image from 'next/image';
import Link from 'next/link';
import { useRouter } from 'next/router';
import type { FormEvent, ReactElement } from 'react';
import { cloneElement, isValidElement, useState } from 'react';
@@ -444,6 +445,19 @@ export function NewProjectPageContent({
</Tooltip>
);
})}
<Text variant="subtitle2">
Select a plan that suits your infrastructure needs.{' '}
<Link href="https://nhost.io/pricing">
<a
href="https://nhost.io/pricing"
className="underline"
target="_blank"
rel="noopener noreferrer"
>
Learn more
</a>
</Link>
</Text>
</RadioGroup>
</div>
</div>

View File

@@ -5,6 +5,7 @@ import { Button } from '@/components/ui/v2/Button';
import { Divider } from '@/components/ui/v2/Divider';
import { GitHubIcon } from '@/components/ui/v2/icons/GitHubIcon';
import { Text } from '@/components/ui/v2/Text';
import { useHostName } from '@/features/projects/common/hooks/useHostName';
import { getToastStyleProps } from '@/utils/constants/settings';
import { nhost } from '@/utils/nhost';
import type { ReactElement } from 'react';
@@ -14,6 +15,8 @@ import { toast } from 'react-hot-toast';
export default function SignUpPage() {
const [loading, setLoading] = useState(false);
const redirectTo = useHostName();
return (
<>
<Text
@@ -24,7 +27,7 @@ export default function SignUpPage() {
It&apos;s time to build
</Text>
<Box className="grid grid-flow-row gap-4 rounded-md border bg-transparent p-6 lg:p-12">
<Box className="grid grid-flow-row gap-4 p-6 bg-transparent border rounded-md lg:p-12">
<Button
className="!bg-white !text-black hover:ring-2 hover:ring-white hover:ring-opacity-50 disabled:!text-black disabled:!text-opacity-60"
startIcon={<GitHubIcon />}
@@ -35,7 +38,10 @@ export default function SignUpPage() {
setLoading(true);
try {
await nhost.auth.signIn({ provider: 'github' });
await nhost.auth.signIn({
provider: 'github',
options: { redirectTo },
});
} catch {
toast.error(
`An error occurred while trying to sign in using GitHub. Please try again later.`,
@@ -61,7 +67,7 @@ export default function SignUpPage() {
<Divider className="!my-2" />
<Text color="secondary" className="text-center text-sm">
<Text color="secondary" className="text-sm text-center">
By clicking continue, you agree to our{' '}
<NavLink
href="https://nhost.io/legal/terms-of-service"

View File

@@ -7283,6 +7283,11 @@ export type GetAssistantsQueryVariables = Exact<{ [key: string]: never; }>;
export type GetAssistantsQuery = { __typename?: 'query_root', graphite?: { __typename?: 'graphiteQuery', assistants: Array<{ __typename?: 'graphiteAssistant', assistantID: string, name: string, description: string, model: string, instructions: string, graphql?: Array<{ __typename?: 'graphiteAssistantToolGraphQL', name: string, query: string, description: string, arguments: Array<{ __typename?: 'graphiteAssistantToolArgument', name: string, type: string, description: string, required: boolean }> }> | null, webhooks?: Array<{ __typename?: 'graphiteAssistantToolWebhook', name: string, URL: string, description: string, arguments: Array<{ __typename?: 'graphiteAssistantToolArgument', name: string, type: string, description: string, required: boolean }> }> | null }> } | null };
export type GetGraphiteSessionsQueryVariables = Exact<{ [key: string]: never; }>;
export type GetGraphiteSessionsQuery = { __typename?: 'query_root', graphite?: { __typename?: 'graphiteQuery', sessions: Array<{ __typename?: 'graphiteSession', sessionID: string }> } | null };
export type InsertAssistantMutationVariables = Exact<{
data: GraphiteAssistantInput;
}>;
@@ -7451,6 +7456,45 @@ export type GetAssistantsQueryResult = Apollo.QueryResult<GetAssistantsQuery, Ge
export function refetchGetAssistantsQuery(variables?: GetAssistantsQueryVariables) {
return { query: GetAssistantsDocument, variables: variables }
}
export const GetGraphiteSessionsDocument = gql`
query getGraphiteSessions {
graphite {
sessions {
sessionID
}
}
}
`;
/**
* __useGetGraphiteSessionsQuery__
*
* To run a query within a React component, call `useGetGraphiteSessionsQuery` and pass it any options that fit your needs.
* When your component renders, `useGetGraphiteSessionsQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useGetGraphiteSessionsQuery({
* variables: {
* },
* });
*/
export function useGetGraphiteSessionsQuery(baseOptions?: Apollo.QueryHookOptions<GetGraphiteSessionsQuery, GetGraphiteSessionsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<GetGraphiteSessionsQuery, GetGraphiteSessionsQueryVariables>(GetGraphiteSessionsDocument, options);
}
export function useGetGraphiteSessionsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetGraphiteSessionsQuery, GetGraphiteSessionsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<GetGraphiteSessionsQuery, GetGraphiteSessionsQueryVariables>(GetGraphiteSessionsDocument, options);
}
export type GetGraphiteSessionsQueryHookResult = ReturnType<typeof useGetGraphiteSessionsQuery>;
export type GetGraphiteSessionsLazyQueryHookResult = ReturnType<typeof useGetGraphiteSessionsLazyQuery>;
export type GetGraphiteSessionsQueryResult = Apollo.QueryResult<GetGraphiteSessionsQuery, GetGraphiteSessionsQueryVariables>;
export function refetchGetGraphiteSessionsQuery(variables?: GetGraphiteSessionsQueryVariables) {
return { query: GetGraphiteSessionsDocument, variables: variables }
}
export const InsertAssistantDocument = gql`
mutation insertAssistant($data: graphiteAssistantInput!) {
graphite {

View File

@@ -14948,6 +14948,7 @@ export type Plans = {
/** An aggregate relationship */
apps_aggregate: Apps_Aggregate;
createdAt: Scalars['timestamptz'];
deprecated: Scalars['Boolean'];
featureBackupEnabled: Scalars['Boolean'];
featureCustomDomainsEnabled: Scalars['Boolean'];
featureCustomEmailTemplatesEnabled: Scalars['Boolean'];
@@ -14961,6 +14962,7 @@ export type Plans = {
/** Max number of functions to deploy per git deployment */
featureMaxNumberOfFunctionsPerDeployment: Scalars['Int'];
id: Scalars['uuid'];
individual: Scalars['Boolean'];
isDefault: Scalars['Boolean'];
isFree: Scalars['Boolean'];
isPublic: Scalars['Boolean'];
@@ -15048,6 +15050,7 @@ export type Plans_Bool_Exp = {
apps?: InputMaybe<Apps_Bool_Exp>;
apps_aggregate?: InputMaybe<Apps_Aggregate_Bool_Exp>;
createdAt?: InputMaybe<Timestamptz_Comparison_Exp>;
deprecated?: InputMaybe<Boolean_Comparison_Exp>;
featureBackupEnabled?: InputMaybe<Boolean_Comparison_Exp>;
featureCustomDomainsEnabled?: InputMaybe<Boolean_Comparison_Exp>;
featureCustomEmailTemplatesEnabled?: InputMaybe<Boolean_Comparison_Exp>;
@@ -15058,6 +15061,7 @@ export type Plans_Bool_Exp = {
featureMaxFilesSize?: InputMaybe<Int_Comparison_Exp>;
featureMaxNumberOfFunctionsPerDeployment?: InputMaybe<Int_Comparison_Exp>;
id?: InputMaybe<Uuid_Comparison_Exp>;
individual?: InputMaybe<Boolean_Comparison_Exp>;
isDefault?: InputMaybe<Boolean_Comparison_Exp>;
isFree?: InputMaybe<Boolean_Comparison_Exp>;
isPublic?: InputMaybe<Boolean_Comparison_Exp>;
@@ -15096,6 +15100,7 @@ export type Plans_Inc_Input = {
export type Plans_Insert_Input = {
apps?: InputMaybe<Apps_Arr_Rel_Insert_Input>;
createdAt?: InputMaybe<Scalars['timestamptz']>;
deprecated?: InputMaybe<Scalars['Boolean']>;
featureBackupEnabled?: InputMaybe<Scalars['Boolean']>;
featureCustomDomainsEnabled?: InputMaybe<Scalars['Boolean']>;
featureCustomEmailTemplatesEnabled?: InputMaybe<Scalars['Boolean']>;
@@ -15109,6 +15114,7 @@ export type Plans_Insert_Input = {
/** Max number of functions to deploy per git deployment */
featureMaxNumberOfFunctionsPerDeployment?: InputMaybe<Scalars['Int']>;
id?: InputMaybe<Scalars['uuid']>;
individual?: InputMaybe<Scalars['Boolean']>;
isDefault?: InputMaybe<Scalars['Boolean']>;
isFree?: InputMaybe<Scalars['Boolean']>;
isPublic?: InputMaybe<Scalars['Boolean']>;
@@ -15200,6 +15206,7 @@ export type Plans_On_Conflict = {
export type Plans_Order_By = {
apps_aggregate?: InputMaybe<Apps_Aggregate_Order_By>;
createdAt?: InputMaybe<Order_By>;
deprecated?: InputMaybe<Order_By>;
featureBackupEnabled?: InputMaybe<Order_By>;
featureCustomDomainsEnabled?: InputMaybe<Order_By>;
featureCustomEmailTemplatesEnabled?: InputMaybe<Order_By>;
@@ -15210,6 +15217,7 @@ export type Plans_Order_By = {
featureMaxFilesSize?: InputMaybe<Order_By>;
featureMaxNumberOfFunctionsPerDeployment?: InputMaybe<Order_By>;
id?: InputMaybe<Order_By>;
individual?: InputMaybe<Order_By>;
isDefault?: InputMaybe<Order_By>;
isFree?: InputMaybe<Order_By>;
isPublic?: InputMaybe<Order_By>;
@@ -15236,6 +15244,8 @@ export enum Plans_Select_Column {
/** column name */
CreatedAt = 'createdAt',
/** column name */
Deprecated = 'deprecated',
/** column name */
FeatureBackupEnabled = 'featureBackupEnabled',
/** column name */
FeatureCustomDomainsEnabled = 'featureCustomDomainsEnabled',
@@ -15256,6 +15266,8 @@ export enum Plans_Select_Column {
/** column name */
Id = 'id',
/** column name */
Individual = 'individual',
/** column name */
IsDefault = 'isDefault',
/** column name */
IsFree = 'isFree',
@@ -15288,6 +15300,7 @@ export enum Plans_Select_Column {
/** input type for updating data in table "plans" */
export type Plans_Set_Input = {
createdAt?: InputMaybe<Scalars['timestamptz']>;
deprecated?: InputMaybe<Scalars['Boolean']>;
featureBackupEnabled?: InputMaybe<Scalars['Boolean']>;
featureCustomDomainsEnabled?: InputMaybe<Scalars['Boolean']>;
featureCustomEmailTemplatesEnabled?: InputMaybe<Scalars['Boolean']>;
@@ -15301,6 +15314,7 @@ export type Plans_Set_Input = {
/** Max number of functions to deploy per git deployment */
featureMaxNumberOfFunctionsPerDeployment?: InputMaybe<Scalars['Int']>;
id?: InputMaybe<Scalars['uuid']>;
individual?: InputMaybe<Scalars['Boolean']>;
isDefault?: InputMaybe<Scalars['Boolean']>;
isFree?: InputMaybe<Scalars['Boolean']>;
isPublic?: InputMaybe<Scalars['Boolean']>;
@@ -15367,6 +15381,7 @@ export type Plans_Stream_Cursor_Input = {
/** Initial value of the column from where the streaming should start */
export type Plans_Stream_Cursor_Value_Input = {
createdAt?: InputMaybe<Scalars['timestamptz']>;
deprecated?: InputMaybe<Scalars['Boolean']>;
featureBackupEnabled?: InputMaybe<Scalars['Boolean']>;
featureCustomDomainsEnabled?: InputMaybe<Scalars['Boolean']>;
featureCustomEmailTemplatesEnabled?: InputMaybe<Scalars['Boolean']>;
@@ -15380,6 +15395,7 @@ export type Plans_Stream_Cursor_Value_Input = {
/** Max number of functions to deploy per git deployment */
featureMaxNumberOfFunctionsPerDeployment?: InputMaybe<Scalars['Int']>;
id?: InputMaybe<Scalars['uuid']>;
individual?: InputMaybe<Scalars['Boolean']>;
isDefault?: InputMaybe<Scalars['Boolean']>;
isFree?: InputMaybe<Scalars['Boolean']>;
isPublic?: InputMaybe<Scalars['Boolean']>;
@@ -15414,6 +15430,8 @@ export enum Plans_Update_Column {
/** column name */
CreatedAt = 'createdAt',
/** column name */
Deprecated = 'deprecated',
/** column name */
FeatureBackupEnabled = 'featureBackupEnabled',
/** column name */
FeatureCustomDomainsEnabled = 'featureCustomDomainsEnabled',
@@ -15434,6 +15452,8 @@ export enum Plans_Update_Column {
/** column name */
Id = 'id',
/** column name */
Individual = 'individual',
/** column name */
IsDefault = 'isDefault',
/** column name */
IsFree = 'isFree',
@@ -15700,6 +15720,12 @@ export type Query_Root = {
getLogsVolume: Metrics;
getPostgresVolumeCapacity: Metrics;
getPostgresVolumeUsage: Metrics;
/**
* Returns list of label values for a given label within a range of time.
*
* If `from` and `to` are not provided, they default to 6 hour ago and now, respectively.
*/
getServiceLabelValues: Array<Scalars['String']>;
getTotalRequests: Metrics;
getUsageAll: Array<UsageSummary>;
/** fetch data from the table: "github_app_installations" using primary key columns */
@@ -16544,6 +16570,11 @@ export type Query_RootGetPostgresVolumeUsageArgs = {
};
export type Query_RootGetServiceLabelValuesArgs = {
appID: Scalars['String'];
};
export type Query_RootGetTotalRequestsArgs = {
appID: Scalars['String'];
from?: InputMaybe<Scalars['Timestamp']>;
@@ -24465,7 +24496,7 @@ export const PrefetchNewAppDocument = gql`
regions(order_by: {city: asc}) {
...PrefetchNewAppRegions
}
plans(order_by: {sort: asc}) {
plans(order_by: {sort: asc}, where: {deprecated: {_eq: false}}) {
...PrefetchNewAppPlans
}
workspaces {

3
docs/.gitignore vendored
View File

@@ -5,7 +5,6 @@
/build
# Generated files
.docusaurus
.cache-loader
# Misc
@@ -18,5 +17,3 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.vercel
docs/reference/docgen

View File

@@ -1,5 +1,17 @@
# @nhost/docs
## 2.1.0
### Minor Changes
- 65b6a48d5: feat: added graphite/cli documentation
## 2.0.0
### Major Changes
- 6d08b3430: New Docs powered by Mintlify
## 0.7.5
### Patch Changes

View File

@@ -1,37 +1,32 @@
# Nhost Docs
# Mintlify Starter Kit
This documentation describes how to build, start and test the documentation locally.
Click on `Use this template` to copy the Mintlify starter kit. The starter kit contains examples including
### Installation
- Guide pages
- Navigation
- Customizations
- API Reference pages
- Use of popular components
```bash
$ pnpm i
### Development
Install the [Mintlify CLI](https://www.npmjs.com/package/mintlify) to preview the documentation changes locally. To install, use the following command
```
npm i -g mintlify
```
### Local Development
Run the following command at the root of your documentation (where mint.json is)
```bash
$ pnpm start
```
mintlify dev
```
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
### Publishing Changes
### Build
Install our Github App to autopropagate changes from youre repo to your deployment. Changes will be deployed to production automatically after pushing to the default branch. Find the link to install on your dashboard.
```bash
$ pnpm build
```
#### Troubleshooting
This command generates static content into the `build` directory and can be served using any static contents hosting service.
### Serve
```bash
$ pnpm serve
```
This command serves the static content from the `build` directory.
### Contributing
All pull requests are greatly appreciated! See our [contributing guide](https://github.com/nhost/nhost/blob/main/CONTRIBUTING.md) to get started.
- Mintlify dev isn't running - Run `mintlify install` it'll re-install dependencies.
- Page loads as a 404 - Make sure you are running in a folder with `mint.json`

View File

@@ -0,0 +1,3 @@
## My Snippet
<Info>This is an example of a reusable snippet</Info>

View File

@@ -1,3 +0,0 @@
module.exports = {
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
};

View File

@@ -0,0 +1,162 @@
---
title: Commands
description: Available commands to run and manage local Nhost projects
icon: rectangle-code
---
## `nhost init`
Initializes current directory with a new project containing configuration and objects necessary to run Nhost locally.
<Accordion title="Output">
Initializing Nhost project
Successfully initialized Nhost project, run `nhost up` to start development
</Accordion>
### Flags
`--remote` optional `false` <br />
Pulls state from a remote Nhost instance.
## `nhost up`
Starts Nhost for development and testing purposes. Requires scaffolding from `nhost init`.
<Accordion title="Output">
```
Nhost development environment started.
URLs:
- Postgres: postgres://postgres:postgres@localhost:5432/local
- Hasura: https://local.hasura.nhost.run
- GraphQL: https://local.graphql.nhost.run
- Auth: https://local.auth.nhost.run
- Storage: https://local.storage.nhost.run
- Functions: https://local.functions.nhost.run
- Dashboard: https://local.dashboard.nhost.run
- Mailhog: https://local.mailhog.nhost.run
SDK Configuration:
Subdomain: local
Region: (empty)
Run `nhost up` to reload the development environment
Run `nhost down` to stop the development environment
Run `nhost logs` to watch the logs
```
</Accordion>
## `nhost down`
Stops all services and deletes all containers.
### Flags
`--volumes` optional `false` <br />
Remove volumes.
## `nhost login`
Login to an Nhost account to perform authenticated operations.
<Accordion title="Output">
- email: user@domain.com
- password:
Authenticating <br />
Successfully logged in, creating PAT <br />
Successfully created PAT <br />
Storing PAT for future user
</Accordion>
### Flags
`--email` optional <br />
Email adress.
`--password` optional <br />
Password.
`--pat` optional <br />
Use this Personal Access Token instead of generating a new one with email/password.
## `nhost logs`
Render all logs.
<Accordion title="Output">
```
app-auth-1 | {"level":"info","message":"Applying SQL migrations..."}
app-auth-1 | {"level":"info","message":"SQL migrations applied"}
app-auth-1 | {"level":"info","message":"Applying metadata..."}
app-auth-1 | {"level":"info","message":"Metadata applied"}
app-auth-1 | {"level":"info","message":"Running on port 4000"}
```
</Accordion>
### Flags
`--follow` optional <br />
Follow (or tail) log output.
`--no-color` optional <br />
Produce monochrome output.
`--no-log-prefix` optional <br />
Don't print prefix in logs.
`--since` optional <br />
Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)
`--tail` optional `all`<br />
Number of lines to show from the end of the logs for each service
`--timestamps` optional <br />
Show timestamps
`--until` optional <br />
Show logs before a timestamp
## `nhost list`
List all remote Nhost projects this user has access to.
<Accordion title="Output">
```
# │ Subdomain │ Project │ Workspace │ Region │
1 │ dahvgwlspuridghplrso │ nbp │ Nuno's Workspace │ eu-central-1 │
2 │ mhmvhergiiycqfvhisan │ Bun Generator │ Nhost Examples │ eu-central-1 │
4 │ qaxzubvwbuhzgxswghug │ Next.js Stripe Starter Template │ Nhost Examples │ eu-central-1 │
5 │ cuzwcdqwgmhbxqetfbci │ Nhost Netlify Starter Template │ Nhost Examples │ us-east-1 │
6 │ jsjiiuwiuqdvvwgwhxnx │ quickstarts │ Nhost Examples │ eu-central-1 │
7 │ vvhjnsgebtspueuuxnvp │ React Apollo │ Nhost Examples │ eu-central-1 │
9 │ vue-quickstart │ vue-quickstart │ Nhost Examples │ eu-central-1 │
11 │ odqlmnqxospbvqvphuyl │ monitoring-app-frankfurt │ monitoring │ eu-central-1 │
12 │ xmhqdbhkvskuubnelgkc │ monitoring-app-london │ monitoring │ eu-west-2 │
13 │ jjetetkbmovfgyewremk │ monitoring-app-mumbai │ monitoring │ ap-south-1 │
```
</Accordion>
## `nhost secrets`
Manage secrets in the Nhost cloud.
### `nhost secrets create`
#### Flags
<Accordion title="Output">
</Accordion>

View File

@@ -0,0 +1,87 @@
---
title: Nhost CLI
description: Tools to develop and test Nhost projects locally
icon: square-terminal
---
## Instaling the Nhost CLI
To install the Nhost CLI copy the following command and paste it into your terminal:
```bash
> sudo curl -L https://raw.githubusercontent.com/nhost/cli/main/get.sh | bash
```
The `get.sh` script checks for both the architecture and operating system and installs the right binary.
### Supported Platforms:
- MacOS
- Linux
- Windows WSL2
### Dependencies:
- [Docker](https://docs.docker.com/get-docker/)
- [curl](https://curl.se/)
- [Git](https://git-scm.com/downloads)
## Updating the Nhost CLI
Update an existing installation to the latest version.
```bash Terminal
> nhost sw upgrade
```
## Running Nhost
Inside of the folder where you want to create your project, run:
```bash Terminal
> nhost init
Initializing Nhost project
Successfully initialized Nhost project, run `nhost up` to start development
```
`init` will scaffold a bunch of files that Nhost uses to manage configuration, database schema and migrations, APIs, functions, etc.
With the project scaffolding in place, start the local Nhost instance with `nhost up`:
```bash Terminal
> nhost up
Nhost development environment started.
URLs:
- Postgres: postgres://postgres:postgres@localhost:5432/local
- Hasura: https://local.hasura.nhost.run
- GraphQL: https://local.graphql.nhost.run
- Auth: https://local.auth.nhost.run
- Storage: https://local.storage.nhost.run
- Functions: https://local.functions.nhost.run
- Dashboard: https://local.dashboard.nhost.run
- Mailhog: https://local.mailhog.nhost.run
SDK Configuration:
Subdomain: local
Region: (empty)
Run `nhost up` to reload the development environment
Run `nhost down` to stop the development environment
Run `nhost logs` to watch the logs
```
The following services are started:
- Postgres
- Hasura Console
- GraphQL API
- Authentication
- Storage
- Functions
- Dashboard
- Mailhog (SMTP server for testing emails locally)
<Info>Support for Nhost Run services is coming soon</Info>

View File

@@ -0,0 +1,32 @@
---
title: 'Overview'
description: 'Developing locally with the Nhost CLI'
icon: hand-wave
---
Run the Nhost Stack on your computer with `nhost init` and `nhost start`. The Nhost CLI ships with all the tools you need to run, test, and operate your projects.
Manage configuration, database migrations, API schema changes, and more, with ease.
```bash
> nhost up
Nhost development environment started.
URLs:
- Postgres: postgres://postgres:postgres@localhost:5432/local
- Hasura: https://local.hasura.nhost.run
- GraphQL: https://local.graphql.nhost.run
- Auth: https://local.auth.nhost.run
- Storage: https://local.storage.nhost.run
- Functions: https://local.functions.nhost.run
- Dashboard: https://local.dashboard.nhost.run
- Mailhog: https://local.mailhog.nhost.run
SDK Configuration:
Subdomain: local
Region: (empty)
Run `nhost up` to reload the development environment
Run `nhost down` to stop the development environment
Run `nhost logs` to watch the logs
```

View File

@@ -1,4 +0,0 @@
{
"label": "Authentication",
"position": 6
}

View File

@@ -1,106 +0,0 @@
---
title: Email Templates
sidebar_position: 4
---
Nhost Auth sends out transactional emails as part of the authentication service. These emails can be modified using email templates.
The following email templates are available:
- **email-verify** - Verify email address
- **email-confirm-change** - Confirm email change to a new email address.
- **signin-passwordless** - Magic Link
- **password-reset** - Reset password
Changing email templates is only available for projects on the [Pro and Enterprise plan](https://nhost.io/pricing).
## Update Email Templates
Your project must be connected to a [Git Repository](/platform/git) to be able to change the email templates.
Email templates are automatically deployed during a deployment, just like database migrations, Hasura metadata, and Serverless Functions.
## File Structure
Emails are located in the `nhost/` folder like this:
The email templates should be provided as body.html and subject.txt files in this predefined folder structure.
**Example:** Email templates for `en` (English) and `es` (Spanish):
```txt
my-nhost-project/
└── nhost/
├── config.yaml
├── emails/
│ ├── en/
│ │ ├── email-verify/
│ │ │ ├── subject.txt
│ │ │ └── body.html
│ │ ├── email-confirm-change/
│ │ │ ├── subject.txt
│ │ │ └── body.html
│ │ ├── signin-passwordless/
│ │ │ ├── subject.txt
│ │ │ └── body.html
│ │ └── password-reset/
│ │ ├── subject.txt
│ │ └── body.html
│ └── es/
│ ├── email-verify/
│ │ ├── subject.txt
│ │ └── body.html
│ ├── email-confirm-change/
│ │ ├── subject.txt
│ │ └── body.html
│ ├── signin-passwordless/
│ │ ├── subject.txt
│ │ └── body.html
│ └── password-reset/
│ ├── subject.txt
│ └── body.html
├── migrations/
├── metadata/
└── seeds
```
As you see, the format is:
```
nhost/emails/{two-letter-language-code}/{email-template}/[subject.txt, body.html]
```
## Languages
The user's language is what decides what template to send. The user's language is stored in the `auth.users` table in the `locale` column. This `locale` column contains a two-letter language code in [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) format.
This value is `en` by default for new users.
## Variables
The following variables are available to use in the email templates:
| Variable | Description |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| link | The full URL to the target of the transaction. This should be used in the main call to action. This is available in all templates. |
| serverUrl | URL of the authentication server |
| clientUrl | URL to your frontend app |
| redirectTo | URL where the user will be redirected to after clicking the link and finishing the action of the email |
| ticket | Ticket that is used to authorize the link request |
| displayName | The display name of the user. |
| email | The email of the user. |
| locale | Locale of the user as a two-letter language code. E.g. "en". |
Use variables like this: `${displayName}` in the email templates.
**Example:** A email template to verify users' emails:
```html title="nhost/emails/en/email-verify/body.html"
<h2>Verify You Email</h2>
<p>Hi, ${displayName}! Please click the link to verify your email:</p>
<p>
<a href="${link}">Verify Email</a>
</p>
```

View File

@@ -1,86 +0,0 @@
---
title: Authentication
sidebar_label: Overview
sidebar_position: 1
image: /img/og/authentication.png
---
Nhost Authentication is a ready-to-use authentication service that is integrated with the [GraphQL API](/graphql) and its permission system from Hasura. This allows you to easily add user authentication to your application without having to build and maintain your own authentication system.
Nhost Authentication lets you authenticate users using different sign-in methods:
- [Email and Password](/authentication/sign-in-with-email-and-password)
- [Magic Link](/authentication/sign-in-with-magic-link)
- [Phone Number (SMS)](/authentication/sign-in-with-phone-number-sms)
- [Security Keys (WebAuthn)](/authentication/sign-in-with-security-keys)
- [Apple](/authentication/sign-in-with-apple)
- [Discord](/authentication/sign-in-with-discord)
- [Facebook](/authentication/sign-in-with-facebook)
- [GitHub](/authentication/sign-in-with-github)
- [Google](/authentication/sign-in-with-google)
- [LinkedIn](/authentication/sign-in-with-linkedin)
- [Spotify](/authentication/sign-in-with-spotify)
- [Twitch](/authentication/sign-in-with-twitch)
- [WorkOS](/authentication/sign-in-with-workos)
## Client URL
Client URL is the URL of your frontend application. The Client URL is used to redirect the user after interacting with any authentication operation, like signing in or resetting their password.
## Allowed Redirect URLs
Allowed Redirect URLs are the URLs of your frontend application that are allowed to redirect the user after interacting with any authentication operation, like signing in or resetting their password. This is useful if you have multiple frontend applications that are using the same Nhost backend or if you want to redirect the user to a specific URL after interacting with an authentication operation.
As an example, for a staging project, you can set the Client URL to `https://staging.example.com` and Allowed Redirect URLs to `https://*.vercel.app`. This way, the user can be redirected to any Vercel deployment of your frontend application.
## Allowed Emails and Domains
Allowed Emails and Domains are used to restrict the sign-up an sign-in process to specific email addresses and domains.
If both allowed emails and allowed domains are set a user can only sign up if their email address matches one of the allowed emails or one of the allowed domains.
## Blocked Emails and Domains
Blocked Emails and Domains are used to block specific email addresses and domains from signing up and singin in.
Note that even if a user's email address matches any allowed email or domain, they will still be blocked if their email address matches any blocked email or domain.
## Multi-factor Authentication
By enabling Multi-factor Authentication (MFA), you can allow users to verify their identity using a second factor during the sign-in process. We currently support Authenticator Apps (TOTP) for MFA.
Once MFA is enabled, a user can enable MFA for their account by scanning a QR code with their Authenticator App. After that, they will be prompted to enter a code generated by their Authenticator App during the sign-in process.
We'll be adding more support in our SDKs and documentation around MFA soon.
## Gravatar
If Gravatar is enabled, Nhost Auth will use the user's email address to fetch their Gravatar profile picture. If the user doesn't have a Gravatar profile picture, a default image will be used.
There are two options for Gravatars:
### Default Image
If the user doesn't have a Gravatar profile picture, a default image will be used. You can choose between the following options:
- `404`: Do not load any image if none is associated with the email hash, instead return an HTTP 404 (File Not Found) response.
- `mp`: (mystery-person) a simple, cartoon-style silhouetted outline of a person (does not vary by email hash).
- `identicon`: a geometric pattern based on an email hash.
- `monsterid`: a generated 'monster' with different colors, faces, etc.
- `wavatar`: generated faces with differing features and backgrounds.
- `retro`: awesome generated, 8-bit arcade-style pixelated faces.
- `robohash`: a generated robot with different colors, faces, etc.
- `blank`: a transparent PNG image.
### Rating
Gravatar images are rated by default. You can choose between the following options:
- `g`: suitable for display on all websites with any audience type.
- `pg`: may contain rude gestures, provocatively dressed individuals, lesser swear words or mild violence.
- `r`: may contain such things as harsh profanity, intense violence, nudity, or hard drug use.
- `x`: may contain hardcore sexual imagery or extremely disturbing violence.
## Disable New Users
If set, newly registered users are disabled and won't be able to sign in. This is useful if you want to manually approve new users before they can sign in.

View File

@@ -1,44 +0,0 @@
---
title: Sign In with Email and Password
sidebar_label: Email and Password
slug: /authentication/sign-in-with-email-and-password
image: /img/og/sign-in-with-email-and-password.png
---
The Email and Password sign-in method is always enabled for all Nhost projects.
## Sign Up
Users must first sign up to be able to sign in.
**Example:** Sign up users using the [Nhost JavaScript client](/reference/javascript).
```js
await nhost.auth.signUp({
email: 'joe@example.com',
password: 'secret-password'
})
```
If you've turned on email verification in your project's **Authentication Settings**, a user will be sent a verification email upon signup. The user must click the verification link in the email before they can sign in.
## Sign In
After the user has successfully signed up, they can sign in.
**Example:** Sign in users using the [Nhost JavaScript client](/reference/javascript).
```js
await nhost.auth.signIn({
email: 'joe@example.com',
password: 'secret-password'
})
```
## Email Verification
If you want to require users to verify their email before they can sign in, you can enable this under **Settings -> Sign-In Methods -> Email and Password** by checking the **Require Verified Emails** checkbox.
If **Require Verified Emails** is enabled, users automatically get a verification email when they sign up. The user must click the verification link in the email before they can sign in. It's possible to edit the ["email-verify" email template](/authentication/email-templates).
It's possible to manually send a verification email to the user using [`nhost.auth.sendVerificationEmail()`](/reference/javascript/auth/send-verification-email).

View File

@@ -1,39 +0,0 @@
---
title: Sign In with Magic Link
sidebar_label: Magic Link
slug: /authentication/sign-in-with-magic-link
image: /img/og/sign-in-with-magic-link.png
---
Nhost allows you to sign in users with a Magic Link, which is a way to sign in users so they don't have to remember a password.
When users sign in using this sign-in method, they'll enter their email address and then receive an email with a (magic) link. When the user clicks on the (magic) link, they get automatically signed in to your app.
The sign-in method is called Magic Link because the user gets "magically" signed in without having to enter a password.
## Configuration
Enable the Magic Link sign-in method in the Nhost dashboard under **Settings -> Sign-In Methods -> Magic Link**.
## Sign In
To sign in users with Magic Link is a two-step process:
1. Send a Magic Link to the user's email address.
2. The user clicks the Magic Link in their email to sign in.
Use the [Nhost JavaScript client](/reference/javascript) to sign in users with Magic Link:
```js
nhost.auth.signIn({
email: 'joe@example.com'
})
```
There is no sign up method for Magic Link. Users will be automatically created when they sign in for the first time.
Users who have signed up with email and password can also sign in with Magic Link.
## Email
It's possible to edit the ["signin-passwordless" email template](/authentication/email-templates).

View File

@@ -1,82 +0,0 @@
---
title: Sign In with Personal Access Tokens
sidebar_label: Personal Access Tokens
slug: /authentication/sign-in-with-personal-access-tokens
---
Nhost allows you to sign in users with personal access tokens (PAT) which is a way to sign in users without an email address or password.
## Configuration
:::info
Personal Access Tokens can only be created through Hasura Auth or the [Nhost JavaScript SDK](/reference/javascript) at the moment.
:::
## Create a Personal Access Token
Users must be signed in to create a personal access token. Once a user is signed in, they can create a personal access token.
**Example:** Create a personal access token:
```tsx
const expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24 * 30) // 30 days
const metadata = { name: 'Example PAT' } // Optional metadata
const { data, error } = await nhost.auth.createPAT(expiresAt, metadata)
// Something unexpected happened
if (error) {
console.error(error)
return
}
console.log(data.id) // The personal access token ID (can be used to delete the token later)
console.log(data.personalAccessToken) // The personal access token
```
Users can create multiple personal access tokens. Each token can have a different expiration date and metadata.
## Sign In
Once a user has created a personal access token, they can use it to sign in.
**Example:** Sign in with a personal access token:
```tsx
const { error, session } = await nhost.auth.signInPAT('<personal-access-token>')
// Something unexpected happened
if (error) {
console.log(error)
return
}
// User is signed in
console.log(session.user)
```
## List or Remove Personal Access Tokens
To list and remove personal access tokens, use GraphQL and set permissions on the `auth.refresh_tokens` table:
**Example:** Get all personal access tokens for a user:
```graphql
query personalAccessTokens($userId: uuid!) {
authRefreshTokens(where: { _and: [{ userId: { _eq: $userId } }, { type: { _eq: pat } }] }) {
id
expiresAt
metadata
}
}
```
**Example:** Remove a personal access token:
```graphql
mutation removePersonalAccessToken($id: uuid!) {
deleteAuthRefreshToken(id: $id) {
id
}
}
```

View File

@@ -1,53 +0,0 @@
---
title: Sign In with Phone Number (SMS)
sidebar_label: Phone Number (SMS)
slug: /authentication/sign-in-with-phone-number-sms
image: /img/og/sign-in-with-phone-number-sms.png
---
Follow this guide to sign in users with a phone number (SMS).
## Configuration
You need a [Twilio account](https://www.twilio.com/try-twilio) to use this feature because all SMS are sent through Twilio.
Enable the Phone Number (SMS) sign-in method in the Nhost dashboard under **Settings -> Sign-In Methods -> Phone Number (SMS)**.
You need to insert the following settings in the Nhost dashboard from Twilio:
- Account SID
- Auth Token
- Messaging Service SID (or a Twilio phone number)
## Sign In
To sign in users with a phone number is a two-step process:
1. Send a one-time password (OTP) to the user's phone number.
2. The user uses the OTP to sign in.
```js
// Step 1: Send OTP to the user's phone number
await nhost.auth.signIn({
phoneNumber: '+11233213123'
})
// Step 2: Sign in user using their phone number and OTP
await nhost.auth.signIn({
phoneNumber: '+11233213123'
// highlight-next-line
otp: '123456',
})
```
The first time a user signs in using a phone number, the user is created. That means you don't need to sign up users before signing in users.
:::info
Phone numbers should start with `+` (not `00`) to follow the [E.164 formatting standard](https://en.wikipedia.org/wiki/E.164).
:::
## Other SMS Providers
We only support Twilio for now. If you want support for another SMS provider, please create an issue on [GitHub](https://github.com/nhost/nhost).

View File

@@ -1,4 +0,0 @@
{
"label": "Sign-In Methods",
"position": 2
}

View File

@@ -1,24 +0,0 @@
---
title: 'Sign-In Methods'
slug: /authentication/sign-in-methods
image: /img/og/sign-in-methods.png
---
Nhost Authentication supports the following sign-in methods:
- [Email and Password](/authentication/sign-in-with-email-and-password)
- [Magic Link](/authentication/sign-in-with-magic-link)
- [Phone Number (SMS)](/authentication/sign-in-with-phone-number-sms)
- [Security Keys (WebAuthn)](/authentication/sign-in-with-security-keys)
- [Apple](/authentication/sign-in-with-apple)
- [Discord](/authentication/sign-in-with-discord)
- [Facebook](/authentication/sign-in-with-facebook)
- [GitHub](/authentication/sign-in-with-github)
- [Google](/authentication/sign-in-with-google)
- [LinkedIn](/authentication/sign-in-with-linkedin)
- [Spotify](/authentication/sign-in-with-spotify)
- [Twitch](/authentication/sign-in-with-twitch)
## Enabling sign-in methods during local development
To enable a sign-in method locally, add variables corresponding to the relevant authentication methods in an `.env.development` file located in the project repository. An overview of available options is available in the [Hasura Auth repository](https://github.com/nhost/hasura-auth/blob/main/docs/environment-variables.md#oauth-environment-variables).

View File

@@ -1,46 +0,0 @@
---
title: Social Providers
sidebar_label: Social Providers
sidebar_position: 10
---
## Enabling Social Sign-In Provider
To start with social sign-in, select your project in Nhost Dashboard and go to **Settings -> Sign-In Methods**.
## Implementing sign-in experience
Use the [Nhost JavaScript SDK](/reference/javascript) and the `signIn()` method to implement social sign-in for your project.
**Example**: Sign in a user with [GitHub](/authentication/sign-in-with-github).
```js
nhost.auth.signIn({
provider: 'github'
})
```
During the sign-in flow, the user is redirected to the provider's website to authenticate. After the user authenticates, they are redirected back to your Nhost project's [**Client URL**](/authentication#client-url) by default. You can change where the user gets redirected to after authentication by passing the `redirectTo` option.
**Example:** Redirect the user to `https://staging.example.com/welcome` after they complete the sign-in flow.
```js
nhost.auth.signIn({
provider: 'github'
options: {
redirectTo: "https://staging.example.com/welcome",
},
})
```
In the example above, it's important to note that the `redirectTo` URL must be part of the [Allowed Redirect URLs](/authentication#allowed-redirect-urls) of your Nhost project.
## Provider OAuth scopes
Scopes are a mechanism in OAuth to allow or limit an application's access to a user's account.
By default, Nhost sets the scope to get the name, email, and avatar URL for each user. Editing scope is not currently supported.
## Provider OAuth Tokens
Nhost saves both access and refresh tokens for each user and provider in the `auth.user_providers` table. These tokens can be used to interact with the provider if needed.

View File

@@ -1,74 +0,0 @@
---
title: Tokens
sidebar_label: Tokens
sidebar_position: 10
image: /img/og/tokens.png
---
Nhost Authentication makes use of two types of tokens:
- **Access token** - used to authenticate a user and access APIs.
- **Refresh token** - used to get a new access token.
Users get both an access token and a refresh token when they sign in.
:::info
If you're using the [Nhost JavaScript client](/reference/javascript), all tokens are automatically set and updated for you. But it can still be good to understand how they work.
:::
## Access Token
An access token (also called [JSON Web Token or JWT](https://en.wikipedia.org/wiki/JSON_Web_Token)) contains information about the user such as the user id. Users send this token to the Nhost services (GraphQL, Auth, Storage, Serverless Functions) to let the services know who's making the request so the services can verify the user's identity and resolve the correct permissions.
The access token is added as an `Authorization` header when making a request, like this:
```http title="Header"
Authorization: Bearer <access_token>
```
Here's an example of an encoded access token:
```
eyJhbGciOiJIUzI1NiJ9.eyJodHRwczovL2hhc3VyYS5pby9qd3QvY2xhaW1zIjp7IngtaGFzdXJhLWFsbG93ZWQtcm9sZXMiOlsibWUiLCJ1c2VyIl0sIngtaGFzdXJhLWRlZmF1bHQtcm9sZSI6InVzZXIiLCJ4LWhhc3VyYS11c2VyLWlkIjoiMTUzODYzZjktZTQwMC00Njg2LTgyMTEtMzI0OGNjYWY2MGJhIiwieC1oYXN1cmEtdXNlci1pcy1hbm9ueW1vdXMiOiJmYWxzZSJ9LCJzdWIiOiIxNTM4NjNmOS1lNDAwLTQ2ODYtODIxMS0zMjQ4Y2NhZjYwYmEiLCJpc3MiOiJoYXN1cmEtYXV0aCIsImlhdCI6MTY1Mzg5MjA5NCwiZXhwIjoxNjUzODkyOTk0fQ.9nVL2Lj8KWBW3WrjJr4tPNH3_29qJKKKSDRNYebhiHI
```
The decoded payload of this access token is a JSON object that looks like this:
```json
{
"https://hasura.io/jwt/claims": {
"x-hasura-allowed-roles": ["me", "user"],
"x-hasura-default-role": "user",
"x-hasura-user-id": "153863f9-e400-4686-8211-3248ccaf60ba",
"x-hasura-user-is-anonymous": "false"
},
"sub": "153863f9-e400-4686-8211-3248ccaf60ba",
"iss": "hasura-auth",
"iat": 1653892094,
"exp": 1653892994
}
```
The token contains information about the user id, default role, allowed roles, if the user is anonymous or not, and other metadata.
The claims under `https://hasura.io/jwt/claims` are the same claims that are used by the GraphQL API to create [permissions](/graphql/permissions). The claims (`x-hasura-*`) are also called permission variables. It's possible to add more [permission variables](/graphql/permissions#permission-variables) to the access token.
:::info
You can manually decode an access token using [JWT.io](https://jwt.io/).
:::
The token is cryptographically signed by Nhost Auth, which means that all other Nhost services can trust the information in the token.
:::info
Use the `NHOST_JWT_SECRET` [system environment variable](/platform/environment-variables#system-environment-variables) to verify access tokens in [Serverless Functions](/serverless-functions). Here's a guide on how to [Get the authenticated user in a Serverless Function](https://github.com/nhost/nhost/discussions/278).
:::
The access token can not be revoked. Instead, the token is only valid for 15 minutes. The user can get a new access token by using the refresh token.
## Refresh Token
A refresh token is used to request a new access token. Refresh tokens are long-lived tokens stored in the database in the `auth.refresh_tokens` table.
Refresh tokens are valid for 30 days.
To revoke a refresh token, simply delete it from the database.

View File

@@ -1,162 +0,0 @@
---
title: Users
sidebar_label: Users
sidebar_position: 1
image: /img/og/users.png
---
Users are stored in the `auth.users` table in the [database](/database).
## Creating Users
Users should be created using the sign-up or sign-in flows as described under [sign-in methods](/authentication/sign-in-methods).
- **Never** create users directly via GraphQL or database, unless you [import users](#import-users) from an external system.
- **Never** modify the database schema for the `auth.users` table.
- **Never** modify the GraphQL root queries or fields for any of the tables in the `auth` schema.
You're allowed to:
- Add and remove your GraphQL relationships for the `users` table and other tables in the `auth` schema.
- Create, edit and delete permissions for the `users` table and other tables in the `auth` schema.
## Roles
Each user has one **default role** and an array of **allowed roles**. These roles are used to resolve permissions for requests to [GraphQL](/graphql/permissions) and [Storage](/storage#permissions).
When the user makes a request, only one role is used to resolve permissions. The default role is used if no role is explicitly specified. Users can only make requests using the default role or one of the allowed roles.
You can manage users' default roles and allowed roles in the Dashboard at **Auth**.
### Default Role
The default role is used when no role is specified in the request. By default, users' default role is `user`.
You can change what the default role for new users should be at **Settings -> Roles and Permissions**.
### Default Allowed Roles
Default Allowed roles are roles the user is allowed to use when making a request. Usually, you would change the role from `user` (the default role) to some other role because you want to use a different role to resolve permissions for a particular request.
By default, users have two allowed roles:
- `user` (also the default role)
- `me`
You can change what the default role for new users should be at **Settings -> Roles and Permissions**.
#### Assign Allowed Roles
It's possible to give users a subset of allowed roles during signup.
**Example:** Only give the `user` role (without the `me` role) for the user's allowed roles:
```js
await nhost.auth.signUp({
email: 'joe@example.com',
password: 'secret-password'
options: {
allowedRoles: ['user']
}
})
```
### Set Role for GraphQL Requests
When no request role is specified, the user's default role will be used:
```js
await nhost.graphql.request(QUERY, {})
```
If you want to make a GraphQL request using a specific role, you can do so by using the `x-hasura-role` header, like this:
```js
await nhost.graphql.request(
QUERY,
{},
{
headers: {
'x-hasura-role': 'me'
}
}
)
```
If the request is not part of the user's allowed roles, the request will fail.
## Metadata
You can store custom information about the user in the `metadata` column of the `users` table. The `metadata` column is of type JSONB so any JSON data can be stored.
**Example:** Add metadata to a user during sign-up:
```js
await nhost.auth.signUp({
email: 'joe@example.com',
password: 'secret-password',
options: {
metadata: {
birthYear: 1989,
town: 'Stockholm',
likes: ['Postgres', 'GraphQL', 'Hasura', 'Authentication', 'Storage', 'Serverless Functions']
}
}
})
```
## Get User Information using GraphQL
**Example:** Get all users.
```graphql
query {
users {
id
displayName
email
metadata
}
}
```
**Example:** Get a single user.
```graphql
query {
user(id: "<user-id>") {
id
displayName
email
metadata
}
}
```
## Import Users
If you have users in a different system, you can import them into Nhost. When importing users you should insert the users directly into the database instead of using the authentication endpoints (`/signup/email-password`) to avoid sending unnecessary transactional emails.
It's possible to insert users via GraphQL or SQL.
### GraphQL
Make a GraphQL request to insert a user like this:
```graphql
mutation insertUser($user: users_insert_input!) {
insertUser(object: $user) {
id
}
}
```
### SQL
Connect directly to the database and insert a user like this:
```sql
INSERT INTO auth.users (id, email, display_name, password_hash, ..) VALUES ('<user-id>', '<email>', '<display-name>', '<password-hash>', ..);
```
Passwords are hashed using [bcrypt](https://en.wikipedia.org/wiki/Bcrypt).

View File

@@ -1,4 +0,0 @@
{
"label": "CLI",
"position": 9
}

View File

@@ -1,38 +0,0 @@
---
title: Nhost CLI
sidebar_label: Overview
sidebar_position: 1
image: /img/og/cli.png
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
The Nhost CLI is used to run and develop your projects locally and deploy changes to the Nhost Platform.
## Installation
Install the CLI with:
```bash
sudo curl -L https://raw.githubusercontent.com/nhost/cli/main/get.sh | bash
```
## Updates
To update the Nhost CLI to its latest version, run the following command:
```bash
sudo nhost sw upgrade
```
### Dependencies
- [Docker](https://www.docker.com/get-started)
## See also
- [Local Development](/cli/local-development)
- [Migrate to Nhost Config](/cli/migrate-config)
- [Multiple Projects in Parallel](/cli/multiple-projects)
- [CLI Documentation](/cli)

View File

@@ -1,4 +0,0 @@
{
"label": "Database",
"position": 4
}

View File

@@ -1,93 +0,0 @@
---
title: 'Event triggers'
sidebar_position: 2
image: /img/og/event-triggers.png
---
Event Triggers enable you to invoke webhooks when a database event happens. Event Triggers are typically used to do post-processing tasks, using custom backend code, based on database events.
Event Triggers are associated with a specific table in the database and the following event types are available:
- **INSERT** - A row is inserted into a table.
- **UPDATE** - A row is updated in a table.
- **DELETE** - A row is deleted from a table.
:::info
It's currently only possible to create Event Triggers in the Hasura Console. We're working on adding support for creating Event Triggers in the Nhost Dashboard.
:::
### Example Use Case
Let's say you're building an e-commerce application and you want to send an email to the customer when a new order is placed. Orders are stored in the `orders` table in your database.
To send out an email every time a new order is placed, you create an event trigger that listens for the `INSERT` event on the `orders` table. Now every time an order is placed, the event trigger invokes a webhook with the order information, and the webhook sends out the email.
## Create Event Trigger
Event Triggers are managed in the Hasura Console. Select **Events** in the main menu and click **Create** to add an Event Trigger.
<video width="99%" autoPlay muted loop controls="true">
<source src="/videos/hasura-create-event-trigger.mp4" type="video/mp4" />
</video>
## Event Triggers and Serverless Functions
Event Triggers and [Serverless Functions](/serverless-functions) are a perfect combination to build powerful database-backend logic. Every Serverless Function is exposed as an HTTP endpoint and can be used as a webhook for Event Triggers.
### Format
When using Serverless Functions as webhooks you should configure the webhook using a combination of environment variables and endpoints like this:
```
{{NHOST_FUNCTIONS_URL}}/orders-insert-send-email
```
![Webhook URL Format](/img/database/event-triggers/webhook-url-format.png)
The `NHOST_FUNCTIONS_URL` is a [system environment variable](/platform/environment-variables#system-environment-variables) and available in production and in development environments using the [CLI](/cli).
### Security
To make sure incoming requests to your webhook comes from Hasura, and not some malicious third party, you can use a shared webhook secret between Hasura and your webhook handler (e.g. your Serverless Function).
It is recommended to use the `NHOST_WEBHOOK_SECRET`, which is a [system environment variable](/platform/environment-variables#system-environment-variables) and available in production and in development environments using the [CLI](/cli). The `NHOST_WEBHOOK_SECRET` is available both in Hasura and in every Serverless Function.
To set this up is a two-step process:
- Step 1: Add the header `nhost-webhook-secret` with the value `NHOST_WEBHOOK_SECRET` (From env var) when creating the Event Trigger in the Hasura Console.
![Webhook Secret Header](/img/database/event-triggers/webhook-secret-header.png)
- Step 2: Check the header `nhost-webhook-secret` for incoming requests and make sure the header is the same as the environment variable `NHOST_WEBHOOK_SECRET`.
Here is an example of how to check the header in a Serverless Function:
```js
export default async function handler(req, res) {
// Check header to make sure the request comes from Hasura
if (req.headers['nhost-webhook-secret'] !== process.env.NHOST_WEBHOOK_SECRET) {
return res.status(400).send('Incorrect webhook secret')
}
// Do something
// Example:
// - Send an email
// - Create a subscription in Stripe
// - Generate a PDF
// - Send a message to Slack or Discord
// - Update some data in the database
console.log(JSON.stringify(req.body, null, 2))
return res.send('OK')
}
```
The `NHOST_WEBHOOK_SECRET` is a [system environment variable](/platform/environment-variables#system-environment-variables) and available in production and in development environments using the [CLI](/cli).
## Next Steps
- Read the full [Event Triggers documentation from Hasura](https://hasura.io/docs/latest/graphql/core/event-triggers/index/).
- Learn about the [GraphQL API](/graphql).

View File

@@ -1,169 +0,0 @@
---
title: 'Database'
sidebar_position: 1
sidebar_label: 'Overview'
image: /img/og/database.png
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
Every Nhost project comes with its own [Postgres database](https://postgres.org/). Postgres is the world's most advanced open-source relational database and it's the most [popular SQL database for developers](https://insights.stackoverflow.com/survey/2021#section-most-loved-dreaded-and-wanted-databases).
There are three ways of managing your database:
1. Nhost Database UI (recommended).
2. Hasura Console.
3. [Connect directly to the database.](#postgres-access)
## Schemas
Generally, you should use the `public` schema for your project. It's also ok to add custom schemas for more advanced usage.
The two schemas `auth` and `storage` are reserved for [Nhost Auth](/authentication) and [Nhost Storage](/storage). You're allowed to modify **permissions** and **add relationships**. However, never modify any tables or remove relationships that were added by Nhost inside the `auth` and `storage` schemas.
## Manage Tables
### Create Table
1. Go to **Database** in the left menu.
2. Click **New table**.
3. Enter a **name** for the table.
4. Add **columns**.
5. Select a **Primary Key** (usually the `id` column).
6. (Optional) Select an **Identity** column. Identity is for integer columns only and is usually selected for the `id` column so the `id` is automatically incremented (1,2,3...) on new rows.
7. (Optional) Add **Foreign Keys**.
8. Click **Create**.
When a table is created it is instantly available through the [GraphQL API](/graphql).
Here's an example of how to create a `customers` table:
<Tabs groupId="nhost-vs-sql">
<TabItem value="nhost" label="Nhost" default>
<video width="99%" autoPlay muted loop controls="true">
<source src="/videos/nhost-table-create.mp4" type="video/mp4" />
</video>
</TabItem>
<TabItem value="sql" label="SQL">
```sql
CREATE TABLE "public"."customers" (
"id" bigint PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
"name" text NOT NULL
);
```
</TabItem>
</Tabs>
### Edit Table
1. Go to the **Database** in the left menu
2. Click on the **context menu** of the table you want to change and click **Edit table**.
3. **Edit** (add, change, delete) the table's columns.
4. Click **Save**.
Here's an example of how to edit a `customers` table by adding an `address` column:
<Tabs groupId="nhost-vs-sql">
<TabItem value="nhost" label="Nhost" default>
<video width="99%" autoPlay muted loop controls="true">
<source src="/videos/nhost-table-edit.mp4" type="video/mp4" />
</video>
</TabItem>
<TabItem value="sql" label="SQL">
```sql
ALTER TABLE "public"."customers" ADD COLUMN "address" text;
```
</TabItem>
</Tabs>
### Delete Table
1. Go to the **Database** in the left menu
2. Click on the **context menu** of the table you want to delete and click **Delete table**.
3. Click **Delete** to confirm deleting the table.
**Example:** Delete a `customers` table:
<Tabs groupId="nhost-vs-sql">
<TabItem value="nhost" label="Nhost" default>
<video width="99%" autoPlay muted loop controls="true">
<source src="/videos/nhost-table-delete.mp4" type="video/mp4" />
</video>
</TabItem>
<TabItem value="sql" label="SQL">
```sql
DROP TABLE "public"."customers";
```
</TabItem>
</Tabs>
## Postgres Access
It's possible to access your Postgres database directly with your favorite Postgres client.
Go to **Settings** in the left menu and click on **Database**. You'll find the connection string and credentials to connect to your database.
![Database Connection Info](/img/database/connection-info.png)
### Reset Postgres Password
It's possible to reset the database password under **Settings** -> **Database** -> **Reset Password**.
<video width="99%" autoPlay muted loop controls="true">
<source src="/videos/nhost-database-reset-password.mp4" type="video/mp4" />
</video>
## Migrations
To track database changes, use the [Nhost CLI](/cli) to develop locally and use our [Git integration](/platform/git) to automatically deploy database migrations live.
1. Develop locally using the Nhost CLI.
2. Push changes to GitHub.
3. Nhost automatically deploys changes.
Learn how to do [development with the Nhost CLI](/cli/local-development).
## Seed Data
Seed data is a way of automatically adding data to your database using SQL when a new environment is created. This is, for the moment, only applicable when you're using the [Nhost CLI](/cli) to develop locally. When you're running `nhost up` for the first time, seed data is added.
In the future, seed data will also be added to new preview environments.
Seed data should be located in `nhost/seeds/default/` and are executed in alphabetical order.
**Example:** Two seed scripts with countries and products.
```text
nhost/seeds/default/001-countries.sql
nhost/seeds/default/002-products.sql
```
## Backups
Databases on the [Pro and Enterprise plans](https://nhost.io/pricing) are automatically backed up daily.
## Best Practices
- Use lower-case names for tables. E.g. `customers` instead of `Customers`.
- Use plural names for tables. E.g. `customers` instead of `customer`.
- use underscore (`_`) instead of camelCase for table names. E.g. `customer_invoices` instead of `customerInvoices`.
- use underscore (`_`) instead of camelCase for column names. E.g. `first_name` instead of `firstName`.
## Next Steps
- [Learn PostgreSQL Tutorial - Full Course for Beginners (YouTube)](https://www.youtube.com/watch?v=qw--VYLpxG4).
- Learn more about how to manage your [Postgres database in Hasura](https://hasura.io/docs/latest/graphql/core/databases/postgres/schema/index/).
- Learn about the [GraphQL API](/graphql).

View File

@@ -1,84 +0,0 @@
---
title: 'Settings'
sidebar_position: 3
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
Below you can find the official schema (cue) and an example to configure your postgres database:
<Tabs groupId="package-manager">
<TabItem value="schema" label="schema">
```cue
#Postgres: {
version: string | *"14.6-20230705-1"
// Resources for the service, optional
resources?: #Resources & {
replicas: 1
}
// postgres settings of the same name in camelCase, optional
settings?: {
jit: "off" | "on" | *"on"
maxConnections: int32 | *100
sharedBuffers: string | *"128MB"
effectiveCacheSize: string | *"4GB"
maintenanceWorkMem: string | *"64MB"
checkpointCompletionTarget: number | *0.9
walBuffers: int32 | *-1
defaultStatisticsTarget: int32 | *100
randomPageCost: number | *4.0
effectiveIOConcurrency: int32 | *1
workMem: string | *"4MB"
hugePages: string | *"try"
minWalSize: string | *"80MB"
maxWalSize: string | *"1GB"
maxWorkerProcesses: int32 | *8
maxParallelWorkersPerGather: int32 | *2
maxParallelWorkers: int32 | *8
maxParallelMaintenanceWorkers: int32 | *2
}
}
```
</TabItem>
<TabItem value="toml" label="toml" default>
```toml
[postgres]
version = '14.6-20230925-1'
[postgres.resources.compute]
cpu = 1000
memory = 2048
[postgres.settings]
jit = "off"
maxConnections = 100
sharedBuffers = '256MB'
effectiveCacheSize = '768MB'
maintenanceWorkMem = '64MB'
checkpointCompletionTarget = 0.9
walBuffers = -1
defaultStatisticsTarget = 100
randomPageCost = 1.1
effectiveIOConcurrency = 200
workMem = '1310kB'
hugePages = 'off'
minWalSize = '80MB'
maxWalSize = '1GB'
maxWorkerProcesses = 8
maxParallelWorkersPerGather = 2
maxParallelWorkers = 8
maxParallelMaintenanceWorkers = 2
```
</TabItem>
</Tabs>
:::info
At the time of writing this document postgres settings are only supported via the [configuration file](https://nhost.io/blog/config).
:::

View File

@@ -1,4 +0,0 @@
{
"label": "GraphQL API",
"position": 5
}

View File

@@ -1,350 +0,0 @@
---
title: 'GraphQL API'
sidebar_position: 1
sidebar_label: 'Overview'
image: /img/og/graphql.png
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
A GraphQL API is automatically and instantly available based on the tables and columns in your [database](/database).
The GraphQL API has support for inserting, selecting, updating, and deleting data, which usually accounts for 80% of all API operations you need.
It's the [Hasura GraphQL engine](https://github.com/hasura/graphql-engine) that powers the GraphQL API which means that all documentation about [queries](https://hasura.io/docs/latest/graphql/core/databases/postgres/queries/index/), [mutations](https://hasura.io/docs/latest/graphql/core/databases/postgres/mutations/index/), and [subscriptions](https://hasura.io/docs/latest/graphql/core/databases/postgres/subscriptions/index/) from Hasura's documentation is applicable.
## What is GraphQL
GraphQL is a query language for APIs that prioritize developer experience. The GraphQL API can be used to both fetch (query) and modify (mutation) data. GraphQL is especially powerful for frontend developers who want to build products fast.
GraphQL has grown rapidly in popularity in the last years and has been adopted by almost all major tech companies such as Facebook, GitHub, and Stripe.
Building your GraphQL API is a lot of work, but with Nhost it's easy because every table and column is instantly available in your GraphQL API.
## Endpoint
The GraphQL API is available at `https://[subdomain].graphql.[region].nhost.run/v1` When using the [CLI](/cli) the GraphQL API is available at `https://local.graphql.nhost.run/v1`.
## GraphQL Clients for JavaScript
The [Nhost JavaScript client](/reference/javascript) comes with a simple [GraphQL client](/reference/javascript/graphql) that works well for the backend or simple applications.
When building more complex frontend applications, we recommend using a more advanced GraphQL client such as:
- [Apollo Client](https://www.apollographql.com/docs/react/):
- [Nhost Apollo Client for React](/reference/react/apollo)
- [Nhost Apollo Client for Vue](/reference/vue/apollo)
- [URQL](https://formidable.com/open-source/urql/)
- [React Query](https://react-query.tanstack.com/graphql)
- [SWR](https://swr.vercel.app/docs/data-fetching#graphql)
## Queries
A query is used to fetch data from the GraphQL API.
:::tip
The [Queries documentation from Hasura](https://hasura.io/docs/latest/graphql/core/databases/postgres/queries/index/) is applicable since we're using Hasura's GraphQL Engine for your project.
:::
**Example:** A GraphQL query to select `title`, `body`, and `isCompleted` for every row in the `todos` table.
<Tabs >
<TabItem value="request" label="Request" default>
```graphql
query GetTodos {
todos {
title
body
isCompleted
}
}
```
</TabItem>
<TabItem value="response" label="Response">
```json
{
"data": {
"todos": [
{
"title": "Delete Firebase account",
"body": "Migrate to Nhost",
"isCompleted": true
}
]
}
}
```
</TabItem>
</Tabs>
#### Filtering and sorting
More complex queries utilize filters, limits, sorting, and aggregation.
Here's an example of a more complex GraphQL query that selects all items in the `todos` table that are not completed, with the total number of comments and the last five comments:
<Tabs >
<TabItem value="request" label="Request" default>
```graphql
query GetTodosWithLatestComments {
todos(where: { isCompleted: { _eq: false } }) {
title
body
comments(limit: 5, order_by: { createdAt: desc }) {
comment
createdAt
author
}
comments_aggregate {
aggregate {
count(columns: id)
}
}
}
}
```
</TabItem>
<TabItem value="response" label="Response">
```json
{
"data": {
"todos": [
{
"title": "Delete Firebase account",
"body": "Migrate to Nhost",
"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
}
}
}
]
}
}
```
</TabItem>
</Tabs>
## Mutations
A GraphQL mutation is used to insert, upsert, update, or delete data.
:::tip
The [Mutations documentation from Hasura](https://hasura.io/docs/latest/graphql/core/databases/postgres/mutations/index/) is applicable since we're using Hasura's GraphQL Engine for your project.
:::
### Insert Data
**Example:** A GraphQL mutation to insert data:
<Tabs >
<TabItem value="request" label="Request" default>
```graphql
mutation InsertTodo {
insert_todos(
objects: [{ title: "Delete Firebase account", body: "Migrate to Nhost", isCompleted: false }]
) {
returning {
id
title
body
isCompleted
}
}
}
```
</TabItem>
<TabItem value="response" label="Response">
```json
{
"data": {
"insert_todos": [
{
"id": "bf4b01ec-8eb6-451b-afac-81f5058ce852",
"title": "Delete Firebase account",
"body": "Migrate to Nhost",
"isCompleted": true
}
]
}
}
```
</TabItem>
</Tabs>
#### Insert Multiple Rows
Use an array of objects to insert multiple rows at the same time.
**Example:** Insert multiple Todos at the same time:
```graphql title=GraphQL
mutation InsertMultipleTodos {
insert_todos(
objects: [
{ title: "Build the front end", body: "Mobile app or website first?", isCompleted: false }
{ title: "Launch 🚀", body: "That was easy", isCompleted: false }
]
) {
returning {
id
title
body
isCompleted
}
}
}
```
### Update Data
You can update existing data with an update mutation. You can update multiple rows at once.
**Example:** A GraphQL mutation to mark a atodo item as completed:
```graphql title=GraphQL
mutation UpdateTodoStatus($id: uuid, $isCompleted: Boolean) {
update_todos(_set: { isCompleted: $isCompleted }, where: { id: { _eq: $id } }) {
returning {
body
isCompleted
title
}
}
}
```
Notice how we are using variables as the `id` and `isDone` variables, which lets us mark any todo as completed or not completed with the same mutation.
### Upsert Data
When you're not sure if a piece of data already exists, use an upsert mutation. It will either insert an object into the database if it doesn't exist or update the fields of an existing object.
Unlike for update mutations, you must pass all columns to an upsert mutation.
To convert your insert mutation to an upsert, you need to add an `on_conflict` property for the GraphQL API to know which fields it should use to find duplicates.
The `on_conflict` key must be a unique key in your database:
```graphql title=GraphQL
mutation UpsertTodo {
insert_todos(
objects: { title: "Delete Firebase account", body: "...", isCompleted: false }
on_conflict: { constraint: todos_title_key, update_columns: [title, isCompleted] }
) {
returning {
id
title
body
isCompleted
}
}
}
```
This will update `body` and `done` of the todos with the title "Delete Firebase account".
#### Conditional upsert
Inserts a new object into a table, or if the primary key already exists, updates columns if the `where` condition is met.
For example, you may want to only update an existing todo if it is not done:
```graphql
mutation UpsertTodo {
insert_todos(
objects: { title: "Delete Firebase account", body: "...", 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, the mutation will be ignored if the object already exists.
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 InsertTodo {
insert_todos(
objects: { title: "Delete Firebase account", body: "...", done: false }
on_conflict: { constraint: todos_title_key, update_columns: [] }
) {
returning {
body
done
id
title
}
}
}
```
In this case, the insert mutation is ignored because a todo with the `title` `"Delete Firebase account"` already exists, and `update_columns` is empty.
### Delete Data
To delete your data, use a delete mutation. This mutation will delete all `todos` where `done` is `true`:
```graphql title="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.
## Subscriptions
GraphQL subscriptions are queries that use WebSockets to keep the data up to date in your app in real-time. You only have to change `query` to `subscription` when constructing the GraphQL document:
```graphql title="GraphQL subscription"
subscription GetTodos {
todos {
title
body
done
}
}
```
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 real-time using GraphQL subscriptions.

View File

@@ -1,4 +0,0 @@
{
"label": "Remote Schemas",
"position": 11
}

View File

@@ -1,260 +0,0 @@
---
title: Stripe GraphQL API
sidebar_label: Stripe
sidebar_position: 2
image: /img/og/graphql.png
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
This package creates a Stripe GraphQL API, allowing for interaction with data at Stripe.
Here's an example of how to use the Stripe GraphQL API to get a list of invoices for a specific Stripe customer:
<Tabs >
<TabItem value="request" label="Request" default>
```graphql
query {
stripe {
customer(id: "cus_xxx") {
id
name
invoices {
data {
id
created
paid
hostedInvoiceUrl
}
}
}
}
}
```
</TabItem>
<TabItem value="response" label="Response">
```json
{
"data": {
"stripe": {
"customer": {
"id": "cus_xxx",
"name": "joe@example.com",
"invoices": {
"data": [
{
"id": "in_1MUmwnCCF9wuB4xxxxxxxx",
"created": 1674806769,
"paid": true,
"hostedInvoiceUrl": "https://invoice.stripe.com/i/acct_xxxxxxx/test_YWNjdF8xS25xV1lDQ0Y5d3VCNGZYLF9ORkhWxxxxxxxxxxxx?s=ap"
}
]
}
}
}
}
}
```
</TabItem>
</Tabs>
It's recommended to add the Stripe GraphQL API as a [Remote Schema in Hasura](https://hasura.io/docs/latest/remote-schemas/index/) and connect data from your database with data in Stripe. By doing so, it's possible to request data from your database and Stripe in a single GraphQL query.
Here's an example of how to use the Stripe GraphQL API to get a list of invoices for a specific Stripe customer. Note that the user data is fetched from your database and the Stripe customer data is fetched from Stripe:
```graphql
query {
users {
# User in your database
id
displayName
userData {
stripeCustomerId # Customer's Stripe Customer Id
stripeCustomer {
# Data from Stripe
id
name
paymentMethods {
id
card {
brand
last4
}
}
}
}
}
}
```
## Get Started
Install the package:
<Tabs groupId="package-manager">
<TabItem value="npm" label="npm" default>
```bash
npm install @nhost/stripe-graphql-js
```
</TabItem>
<TabItem value="yarn" label="Yarn">
```bash
yarn install @nhost/stripe-graphql-js
```
</TabItem>
<TabItem value="pnpm" label="pnpm">
```bash
pnpm add @nhost/stripe-graphql-js
```
</TabItem>
</Tabs>
## Serverless Function
Create a new [Serverless Function](/serverless-functions): `functions/graphql/stripe.ts`:
```ts
import { createStripeGraphQLServer } from '@nhost/stripe-graphql-js'
const server = createStripeGraphQLServer()
export default server
```
> You can run the Stripe GraphQL API in any Node.js environment because it's built using [GraphQL Yoga](https://github.com/dotansimha/graphql-yoga).
## Stripe Secret Key
Add `STRIPE_SECRET_KEY` as an environment variable.
If you're using Nhost, add `STRIPE_SECRET_KEY` to `nhost.toml` like this:
```
[[ global.environment ]]
name=STRIPE_SECRET_KEY
value='{{ secrets.STRIPE_SECRET_KEY }}'
```
And then add to your `.secrets` file:
```
STRIPE_SECRET_KEY=sk_test_***
```
In production set your secret with your stripe production key (`sk_live_***`) in the Nhost dashboard.
Learn more about [Stripe API keys](https://stripe.com/docs/keys#obtain-api-keys).
## Start Nhost
```
nhost up
```
Learn more about the [Nhost CLI](/cli).
## Test
Test the Stripe GraphQL API in the browser:
[https://local.functions.nhost.run/v1/graphql/stripe](https://local.functions.nhost.run/v1/graphql/stripe)
## Remote Schema
Add the Stripe GraphQL API as a Remote Schema in Hasura.
**URL**
```
{{NHOST_FUNCTIONS_URL}}/graphql/stripe
```
**Headers**
```
x-nhost-webhook-secret: NHOST_WEBHOOK_SECRET (From env var)
```
> The `NHOST_WEBHOOK_SECRET` is used to verify that the request is coming from Nhost. The environment variable is a [system environment variable](/platform/environment-variables#system-environment-variables) and is always available.
![Hasura Remote Schema](/img/graphql/remote-schemas/stripe/remote-schema.png)
## Permissions
Here's a minimal example without any custom permissions. Only requests using the `x-hasura-admin-secret` header will work:
```js
const server = createStripeGraphQLServer()
```
For more granular permissions, you can pass an `isAllowed` function to the `createStripeGraphQLServer`. The `isAllowed` function takes a `stripeCustomerId` and [`context`](#context) as parameters and runs every time the GraphQL server makes a request to Stripe to get or modify data for a specific Stripe customer.
Here is an example of an `isAllowed` function:
```ts
import { createStripeGraphQLServer } from '@nhost/stripe-graphql-js'
const isAllowed = (stripeCustomerId: string, context: Context) => {
const { isAdmin, userClaims } = context
// allow all requests if they have a valid `x-hasura-admin-secret`
if (isAdmin) {
return true
}
// get user id
const userId = userClaims['x-hasura-user-id']
// check if the user is signed in
if (!userId) {
return false
}
// get more user information from the database
const { user } = await gqlSDK.getUser({
id: userId
})
if (!user) {
return false
}
// check if the user is part of a workspace with the `stripeCustomerId`
return user.workspaceMembers.some((workspaceMember) => {
return workspaceMember.workspace.stripeCustomerId === stripeCustomerId
})
}
const server = createStripeGraphQLServer({ isAllowed })
export default server
```
### Context
The `context` object contains:
- `userClaims` - verified JWT claims from the user's access token.
- `isAdmin` - `true` if the request was made using a valid `x-hasura-admin-secret` header.
- `request` - [Fetch API Request object](https://developer.mozilla.org/en-US/docs/Web/API/Request) that represents the incoming HTTP request in platform-independent way. It can be useful for accessing headers to authenticate a user
- `query` - the DocumentNode that was parsed from the GraphQL query string
- `operationName` - the operation name selected from the incoming query
- `variables` - the variables that were defined in the query
- `extensions` - the extensions that were received from the client
Read more about the [default context from GraphQL Yoga](https://www.the-guild.dev/graphql/yoga-server/docs/features/context#default-context).
## Source Code
The source code is available on [GitHub](https://github.com/nhost/nhost/tree/main/integrations/stripe-graphql-js).

View File

@@ -1,58 +0,0 @@
---
title: 'Introduction to Nhost'
sidebar_label: Introduction
image: /img/og/introduction-to-nhost.png
---
Nhost is the open source GraphQL backend (Firebase Alternative) and a development platform. Nhost is doing for the backend, what [Netlify](https://netlify.com/) and [Vercel](https://vercel.com/) are doing for the frontend.
We provide a modern backend with the general building blocks required to build fantastic digital products.
We make it easy to build and deploy this backend using our platform which takes care of configuration, security, and performance. Things just work and scale automatically so you can focus on your product and your business.
## Quickstart
Get started quickly by following one of our quickstart guides:
- [Next.js](/quickstarts/nextjs)
- [React](/quickstarts/react)
- [RedwoodJS](/quickstarts/redwoodjs)
- [Vue](/quickstarts/vue)
## Products and Features
Learn more about the product and features of Nhost.
- [Database](/database)
- [GraphQL API](/graphql)
- [Authentication](/authentication)
- [Storage](/storage)
- [Serverless Functions](/serverless-functions)
- [Run](/run)
## Architecture
Nhost is a Backend-as-a-Service built with open source tools to provide developers the general building blocks required to build fantastic digital apps and products.
Here's a diagram of the Nhost stack on a high level:
![Nhost Architecture Diagram](/img/architecture/nhost-diagram.png)
As you see in the image above, Nhost provides endpoints for:
- GraphQL API (`/graphql`)
- Authentication (`/auth`)
- Storage (`/storage`)
- Functions (`/functions`)
Data is stored in Postgres and files are stored in S3.
## Open Source
The open source tools used for the full Nhost stack are:
- Database: [Postgres](https://www.postgresql.org/)
- GraphQL API: [Hasura](https://github.com/hasura/graphql-engine)
- Authentication: [Hasura Auth](https://github.com/nhost/hasura-auth)
- Storage: [Hasura Storage](https://github.com/nhost/hasura-storage)
- Functions: [Node.js](https://nodejs.org/en/)

View File

@@ -1,57 +0,0 @@
---
title: 'Introduction to Nhost'
sidebar_label: Introduction
image: /img/og/introduction-to-nhost.png
---
Nhost is the open source GraphQL backend (Firebase Alternative) and a development platform. Nhost is doing for the backend, what [Netlify](https://netlify.com/) and [Vercel](https://vercel.com/) are doing for the frontend.
We provide a modern backend with the general building blocks required to build fantastic digital products.
We make it easy to build and deploy this backend using our platform which takes care of configuration, security, and performance. Things just work and scale automatically so you can focus on your product and your business.
## Quickstart
Get started quickly by following one of our quickstart guides:
- [Next.js](/quickstarts/nextjs)
- [React](/quickstarts/react)
- [RedwoodJS](/quickstarts/redwoodjs)
- [Vue](/quickstarts/vue)
## Products and Features
Learn more about the product and features of Nhost.
- [Database](/database)
- [GraphQL API](/graphql)
- [Authentication](/authentication)
- [Storage](/storage)
- [Serverless Functions](/serverless-functions)
## Architecture
Nhost is a Backend-as-a-Service built with open source tools to provide developers the general building blocks required to build fantastic digital apps and products.
Here's a diagram of the Nhost stack on a high level:
![Nhost Architecture Diagram](/img/architecture/nhost-diagram.png)
As you see in the image above, Nhost provides endpoints for:
- GraphQL API (`/graphql`)
- Authentication (`/auth`)
- Storage (`/storage`)
- Functions (`/functions`)
Data is stored in Postgres and files are stored in S3.
## Open Source
The open source tools used for the full Nhost stack are:
- Database: [Postgres](https://www.postgresql.org/)
- GraphQL API: [Hasura](https://github.com/hasura/graphql-engine)
- Authentication: [Hasura Auth](https://github.com/nhost/hasura-auth)
- Storage: [Hasura Storage](https://github.com/nhost/hasura-storage)
- Functions: [Node.js](https://nodejs.org/en/)

View File

@@ -1,4 +0,0 @@
{
"label": "Platform",
"position": 10
}

View File

@@ -1,40 +0,0 @@
---
title: 'Compute Resources'
sidebar_position: 1
image: /img/og/platform/compute-resources.png
---
Compute resources are the fundamental units that represent the processing power and memory available to your Nhost projects. The primary compute resources are vCPU and RAM. This documentation outlines the key aspects of compute resources in the context of the Nhost Cloud Platform.
### Shared vs Dedicated Compute
Free Projects are given a total of 2 shared vCPUs and 1 GiB of RAM:
- Postgres: 0.5 vCPU / 256 MiB
- Hasura GraphQL: 0.5 vCPU / 384 MiB
- Auth: 0.5 vCPU / 256 MiB
- Storage: 0.5 vCPU / 128 MiB
Pro Projects are given a total of 2 shared vCPUs and 2 GiB of RAM:
- Postgres: 0.5 vCPU / 512 MiB
- Hasura GraphQL: 0.5 vCPU / 768 MiB
- Auth: 0.5 vCPU / 384 MiB
- Storage: 0.5 vCPU / 384 MiB
This is fine if your apps mostly run at low to medium load, occasionally burst for brief periods of time, and can tolerate drops in performance. It is important to understand that the availability of CPU time is not guaranteed.
### Dedicated Compute
On the other hand, for high production workloads where latency is important, or variable performance is not at all tolerable, you should consider configuring your project to use dedicated compute resources.
With dedicated compute, resources are guaranteed for your project so you don't have to contend for them.
In addition to the resources fully dedicated to the project, apps are allowed to burst if demand requires it and resources are available. If properly sized, dedicated resources should guarantee the performance of your application while allowing for occassional burts.
To configure dedicated compute to your projects, all you have to do is navigate to the project's settings, and click on "Compute Resources" (see image below). There you will be able to choose the total amount of resources you want to dedicate, and spread those resources amongst all services.
![Compute Resources](/img/platform/compute-resources/dashboard-slider.png)
To further improve availability and fault tolerance, you can also leverage Service Replicas. To learn more, check out the documentation for [Service Replicas](https://docs.nhost.io/platform/service-replicas).

View File

@@ -1,89 +0,0 @@
---
title: 'Environment Variables'
sidebar_position: 2
image: /img/og/platform/environment-variables.png
---
Environment Variables are key-value pairs configured outside your source code. They are used to store environment-specific values such as API keys.
Environment Variables are available for:
- [Hasura GraphQL Engine](/graphql)
- [Serverless Functions](/serverless-functions)
When an Environment Variable has updated the changes happen immediately for Hasura GraphQL Engine. For Serverless Functions, a new deployment via [Git](/platform/git) is required.
## Custom Environment Variables
You can manage your project's Environment Variables in the Nhost Dashboard or by using the configuration file.
### Dashboard
![Environment Variables](/img/platform/environment-variables/environment-variables.png)
Environment Variables can be managed in the Nhost Dashboard under **Settings** &rarr; **Environment Variables**.
### Configuration File
Environment Variables can also be managed by adding new `[[global.environment]]` sections to the `nhost.toml` file.
```toml
[global]
[[global.environment]]
name = 'MY_ENV_VAR'
value = '<first-value>'
[[global.environment]]
name = 'MY_OTHER_ENV_VAR'
value = '<second-value>'
# ... omitted for brevity
```
These environment variables will also be available on the Nhost Dashboard after committing and pushing the changes to your Git repository.
## System Environment Variables
System environment variables are automatically generated from the configuration file and your project's subdomain and region.
The following system environment variables are available:
- `NHOST_ADMIN_SECRET`
- `NHOST_WEBHOOK_SECRET`
- ~~`NHOST_BACKEND_URL`~~ ([deprecated](https://github.com/nhost/nhost/discussions/1319))
- `NHOST_SUBDOMAIN`
- `NHOST_REGION`
- `NHOST_HASURA_URL`
- `NHOST_AUTH_URL`
- `NHOST_GRAPHQL_URL`
- `NHOST_STORAGE_URL`
- `NHOST_FUNCTIONS_URL`
- `NHOST_JWT_SECRET`
`NHOST_ADMIN_SECRET`, `NHOST_WEBHOOK_SECRET` and `NHOST_JWT_SECRET` are populated with values from the configuration file. The rest of the system environment variables are populated with values from your project's subdomain and region.
**Example values**:
```text
NHOST_ADMIN_SECRET=e7w36ag287qn5qry795f6ymm57qgvqup
NHOST_WEBHOOK_SECRET=ns3sfjgdw4y6zeqthwnnw347dzh8wyj4
NHOST_BACKEND_URL=https://abc123abc.nhost.run
NHOST_SUBDOMAIN=abv123abc
NHOST_REGION=eu-central-1
NHOST_HASURA_URL=https://abc123abc.hasura.eu-central-1.nhost.run/console
NHOST_AUTH_URL=https://abc123abc.auth.eu-central-1.nhost.run/v1
NHOST_GRAPHQL_URL=https://abc123abc.graphql.eu-central-1.nhost.run/v1
NHOST_STORAGE_URL=https://abc123abc.storage.eu-central-1.nhost.run/v1
NHOST_FUNCTIONS_URL=https://abc123abc.functions.eu-central-1.nhost.run/v1
NHOST_JWT_SECRET={"type": "HS256", "key": "vumpbe2w2mgaqj5yqfp7dvxu6kywtvsgb68ejpdaqxerea8jwrsszdp2dhkjxsh4df69pzm3ja6ukedx8ja43zdt6q9kgbgg2w9vh2sedeppukud9a2qzy29v3afdn7m"}
```

View File

@@ -1,54 +0,0 @@
---
title: 'Git'
sidebar_position: 2
image: /img/og/platform/github-integration.png
---
Nhost allows you to automatically deploy your Nhost project when you do changes to your Git repository.
## Supported Git Providers
- GitHub
Support GitLab, BitBucket, and other Git providers are on our roadmap.
## Deployment
The following things are deployed:
- Database migrations
- Hasura metadata
- Serverless Functions
- Email Templates
:::caution
Settings in `nhost/config.yaml` are **not** deployed. That means you need to manually sync settings between local and remote environments between the CLI and Nhost Cloud.
:::
## Using GitHub
1. From your Nhost project, click **Connect to Github**.
![Connect to GitHub](/img/platform/git/connect-to-github.png)
2. **Install the Nhost project** on your Github account.
![Install the Nhost GitHub App](/img/architecture/cli/connect-repo-step-2.png)
3. **Connect** your Github repository.
![Select reopsitoru](/img/architecture/cli/connect-repo-step-3.png)
## Deployment Branch
Nhost only deploys changes from the **Deployment Branch**. By default, your deployment branch matches the default branch (usually `main`).
You can change the Deployment Branch on the **Git** page in the **Settings** section.
It's possible to have multiple Nhost projects connected to the same Git repository and use different Deployment Branches (e.g., `main` and `staging`). Learn more about [multiple environments](/platform/multiple-environments).
## Base Directory
If your Nhost project is not located at the root of your Git repository, which is typically the case when using a monorepo, it's possible to set a custom Base Directory. The Base Directory is where the `nhost/` directory is located. In other words, the Base Directory is the **parent directory** of the `nhost/` directory.
You can change the Base Directory on the **Git** page in the **Settings** section.

View File

@@ -1,35 +0,0 @@
---
title: 'Metrics (beta)'
sidebar_position: 1
image: /img/og/platform/metrics.png
---
Metrics provide insights into your Nhost projects by integrating a managed Grafana instance tailored to them. This feature is available on the Pro plan and allows you to analyze your project's performance, identify potential bottlenecks, and optimize your application.
### Available Dashboards
Nhost Metrics includes a few pre-configured dashboards with the following metrics:
- vCPU/memory usage by Service replica for all services
- Throttling time / percentage
- Postgres volume usage
- Networking
- [Functions](/serverless-functions) metrics like calls, response times, errors, etc.
![Metrics](/img/platform/metrics/grafana.png)
### Accessing Metrics
Metrics can be found in your project's dashboard.
![Metrics](/img/platform/metrics/nhost-dashboard-metrics.png)
### Limitations (Beta)
Please note that while Metrics is in beta, its functionality and pricing might change.
- Users cannot save or use custom dashboards in the current beta version.
- Additional categories of metrics like application and database metrics will be added in future updates.
- Future updates will centralize logs using Loki and integrate alerts directly into the Grafana dashboard.
Using Nhost Metrics allows you to identify bottlenecks in your applications, which you can then fix by leveraging [Compute Resources](https://docs.nhost.io/platform/compute) and [Service Replicas](https://docs.nhost.io/platform/service-replicas) to address these performance issues.

View File

@@ -1,51 +0,0 @@
---
title: 'Multiple Environments'
sidebar_position: 3
---
Here's a guide on how to use Nhost with multiple environments and how to set up a workflow around them.
For this example, we'll set up one **production environment** and one **staging environment**.
## Git
Create a new Git repo and use the [CLI](/cli) and the `nhost init` command to initialize a new project.
Also, create a new branch called `staging`.
You should now have a Git repo with a `main` branch and a `staging` branch.
## Nhost Cloud
To have two environments, we need to create two projects on Nhost Cloud.
Both projects must be connected to the **same** [Git](/platform/git) repo, but using **different** [deployment branches](/platform/git#deployment-branch).
Set your production project to use the `main` branch as the Deployment Branch and your staging project to use the `staging` branch as the Deployment Branch. This way, the production project will only deploy new changes to the `main` branch and the staging project will only deploy new changes to the `staging` branch.
## Development
### Local
Now, use the CLI to do [local development](/cli/local-development). And use a specific feature branch while doing development.
### Staging
Once you're ready to test your changes to staging, create a pull request from your feature branch to the `staging` branch. Then, merge the pull request to `staging`.
This will automatically trigger a new deployment to the staging project on the Nhost platform.
### Production
Once you've tested your changes in the staging environment, you can create a new pull request from the `staging` branch to the `main` branch. Then, merge the pull request to `main`.
This will automatically trigger a new deployment to the production project on the Nhost platform.
## Configuration Overlays
While Nhost uses a single file to deploy all of the environments connected to the same repository and branch, overlays allow you to accommodate for minor differences in those environments by allowing you to define rules to modify the base configuration. For details on overlays head to [Configuration Overlays](/cli/overlays)
## Summary
Now you have two environments, one for staging and one for production. You can use this workflow to do local development, and test your changes in a staging environment before deploying them to production.

View File

@@ -1,57 +0,0 @@
---
title: 'Service Replicas'
sidebar_position: 1
image: /img/og/platform/service-replicas.png
---
Service Replicas is a feature that allows you to create multiple replicas of your services, enhancing availability and fault tolerance for your Nhost apps. By distributing user requests among replicas, your apps can handle more traffic and provide a better user experience. This documentation touches on the key aspects of Service Replicas in the context of the Nhost Cloud Platform.
![Service Replicas](/img/platform/service-replicas/replicas-diagram.png)
To read the announcement, check out our [blog post](https://nhost.io/blog/service-replicas).
### Supported Services
Replicas can be configured for the following services:
- Hasura
- Auth
- Storage
Currently, we don't support replicas for Postgres.
### Configuring Service Replicas
To configure Service Replicas for your project, follow these steps:
1. Navigate to your project's settings in the Nhost Dashboard.
2. Click on "Compute Resources".
3. Locate the "Replicas" slider for each service (Hasura, Auth, and Storage).
4. Adjust the number of replicas for each service as needed.
5. Save your changes.
Please note that when setting multiple replicas for a service, the 1:2 ratio between CPU and RAM for that service has to be respected. With only one replica, this ratio does not need to be respected at the service level.
### Benefits
- Improved fault tolerance: Multiple replicas ensure that if one instance crashes duo to an unexpected issue, the other replicas can continue to serve user requests.
- Improved availability: Distributing user requests among multiple replicas allows your apps to handle more traffic and maintain a high level of performance.
- Load balancing: Distributing workloads evenly among replicas to prevent bottlenecks and ensure smooth performance during peak times.
### Pricing
Pricing is based on the size of each replica and the total number of replicas you have configured for each service:
- If you allocate 1 vCPU and 2 GiB of RAM to the auth service, for example, the cost for a single replica is $50.
- If you configure 3 replicas for auth, the total cost for all replicas would be $150 (3 replicas x $50 per replica).
Please note that Service Replicas is available only for projects on the Pro plan or higher.
### Caveats
- Postgres replication: As mentioned earlier, we do not have support for multiple replicas of Postgres. This feature may be added in the future.
- Resource ratio: When configuring multiple replicas for a service, you must adhere to the 1:2 ratio between CPU and RAM for that service.
![Compute Resources](/img/platform/compute-resources/dashboard-slider.png)
For more information on compute resources and scaling your apps, refer to our documentation on [Compute Resources](https://docs.nhost.io/platform/compute).

View File

@@ -1,5 +0,0 @@
{
"label": "Quickstarts",
"position": 3,
"collapsed": false
}

View File

@@ -1,707 +0,0 @@
---
title: 'Quickstart: Next.js'
sidebar_label: Next.js
sidebar_position: 2
image: /img/og/quickstart-nextjs.png
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
# Quickstart: Next.js
## Introduction
This quickstart provides the steps you need to build a Next.js app
powered by Nhost for the backend. It includes:
- Database: [PostgreSQL](https://www.postgresql.org/)
- Instant GraphQL API: [Hasura](https://hasura.io/)
- Authentication: [Hasura Auth](https://github.com/nhost/hasura-auth/)
- Storage: [Hasura Storage](https://hub.docker.com/r/nhost/hasura-storage)
By the end of this guide, you'll have a full-stack app that allows users to sign
in to access a protected dashboard and update their profile information.
## Prerequisites
Before getting started, let's make sure that your development environment is
ready.
You'll need **Node.js** version 12 or later: [install it from here](https://nodejs.org/en/).
## Project setup
### Create a new Nhost app
import CreateApp from '@site/src/components/create-nhost-project.mdx'
<CreateApp />
:::info
You can also connect your Nhost project to a Git repository at GitHub. When you do this, any updates you push to your code will automatically be deployed. [Learn more](/platform/git)
:::
## Initialize the app
### Create a Next.js app
The simplest way to create a new Next.js application is by using the tool called
`create-next-app`, which bootstraps a Next.js app for you without the hassle of
configuring everything yourself.
So, open your terminal, and run the following command:
```bash
npx create-next-app my-nhost-app --example "https://github.com/nhost/quickstart-nextjs"
```
:::info
This command uses an [existing template](https://github.com/nhost/quickstart-nextjs), through the `--example` flag, which already contains the React components and pages we'll use for this guide.
:::
You can now `cd` into your project directory:
```bash
cd my-nhost-app
```
And run the development server with the following command:
<Tabs groupId="package-manager">
<TabItem value="npm" label="npm" default>
```bash
npm run dev
```
</TabItem>
<TabItem value="yarn" label="Yarn">
```bash
yarn dev
```
</TabItem>
</Tabs>
If everything is working fine, your Next.js development server should be running
on port 3000. Open [http://localhost:3000](http://localhost:3000) from your
browser to check this out.
### Configure Nhost with Next.js
To work with Nhost from within our Next.js app, we'll use the
[Next.js SDK](https://github.com/nhost/nhost/tree/main/packages/nextjs) provided
by Nhost. It's a wrapper around the
[Nhost React SDK](https://github.com/nhost/nhost/tree/main/packages/react) which
gives us a way to interact with our Nhost backend using React hooks.
You can install the Nhost Next.js SDK with:
<Tabs groupId="package-manager">
<TabItem value="npm" label="npm" default>
```bash
npm install @nhost/nextjs graphql
```
</TabItem>
<TabItem value="yarn" label="Yarn">
```bash
yarn add @nhost/nextjs graphql
```
</TabItem>
</Tabs>
Next, open your `_app.js` file as we'll now configure Nhost inside our app.
The Nhost Next.js SDK comes with a React provider named `NhostProvider` that
makes the authentication state and all the provided React hooks available in our
application.
Use the following code to instantiate a new Nhost client and link it to your
Nhost backend:
```jsx title="pages/_app.js"
import { UserProvider } from '../UserProvider';
// highlight-start
import { NhostProvider, NhostClient } from '@nhost/nextjs';
// highlight-end
// highlight-start
const nhost = new NhostClient({
subdomain: process.env.NEXT_PUBLIC_NHOST_SUBDOMAIN || '',
region: process.env.NEXT_PUBLIC_NHOST_REGION || ''
});
// highlight-end
function MyApp({ Component, pageProps }) {
return (
{/* highlight-next-line */}
<NhostProvider nhost={nhost} initial={pageProps.nhostSession}>
<UserProvider>
{/* ... */}
</UserProvider>
{/* highlight-next-line */}
</NhostProvider>
);
}
```
Finally, store the environment variables for `subdomain` and `region` in `.env.development`:
```yaml title=".env.development"
NEXT_PUBLIC_NHOST_SUBDOMAIN=[subdomain]
NEXT_PUBLIC_NHOST_REGION=[region]
```
You find your Nhost project's `subdomain` and `region` in the [project overview](https://app.nhost.io):
![Project Overview](/img/quickstarts/app-dashboard.png)
:::caution
Don't forget to restart your Next.js server after saving your `.env.development`
file to load your new environment variable.
:::
:::info Nhost CLI
Do you use the Nhost CLI? Learn how to set `subdomain` and `region` in the [CLI documentation](/cli#subdomain-and-region).
:::
## Build the app
### Add authentication
#### 1. Sign-up
The next step is to allow our users to authenticate into our application.
Let's start with implementing the sign-up process.
For that, we'll use the `useSignUpEmailPassword` hook provided by the Nhost
Next.js SDK within our `SignUp` component.
So, open up the corresponding file from your project, and use the following
code:
```jsx title="components/SignUp.js"
import styles from '../styles/components/SignUp.module.css'
import { useState } from 'react'
import { useRouter } from 'next/router'
import { useSignUpEmailPassword } from '@nhost/nextjs'
import Link from 'next/link'
import Image from 'next/image'
import Input from './Input'
import Spinner from './Spinner'
const SignUp = () => {
const [firstName, setFirstName] = useState('')
const [lastName, setLastName] = useState('')
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const router = useRouter()
const { signUpEmailPassword, isLoading, isSuccess, needsEmailVerification, isError, error } =
useSignUpEmailPassword()
const handleOnSubmit = async (e) => {
e.preventDefault()
await signUpEmailPassword(email, password, {
displayName: `${firstName} ${lastName}`.trim(),
metadata: {
firstName,
lastName
}
})
}
if (isSuccess) {
router.push('/')
return null
}
const disableForm = isLoading || needsEmailVerification
return (
<div className={styles.container}>
<div className={styles.card}>
<div className={styles['logo-wrapper']}>
<Image src="/logo.svg" alt="logo" layout="fill" objectFit="contain" />
</div>
{needsEmailVerification ? (
<p className={styles['verification-text']}>
Please check your mailbox and follow the verification link to verify your email.
</p>
) : (
<form onSubmit={handleOnSubmit} className={styles.form}>
<div className={styles['input-group']}>
<Input
label="First name"
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
disabled={disableForm}
required
/>
<Input
label="Last name"
value={lastName}
onChange={(e) => setLastName(e.target.value)}
disabled={disableForm}
required
/>
</div>
<Input
type="email"
label="Email address"
value={email}
onChange={(e) => setEmail(e.target.value)}
disabled={disableForm}
required
/>
<Input
type="password"
label="Create password"
value={password}
onChange={(e) => setPassword(e.target.value)}
disabled={disableForm}
required
/>
<button type="submit" disabled={disableForm} className={styles.button}>
{isLoading ? <Spinner size="sm" /> : 'Create account'}
</button>
{isError ? <p className={styles['error-text']}>{error?.message}</p> : null}
</form>
)}
</div>
<p className={styles.text}>
Already have an account?{' '}
<Link href="/sign-in">
<a className={styles.link}>Sign in</a>
</Link>
</p>
</div>
)
}
export default SignUp
```
By default, the user must verify his email address before fully signing up. You can change this setting from your Nhost dashboard.
#### 2. Sign-in
Now that new users can sign up for our application, let's see how to allow
existing users to sign in with email and password.
For that, we will use the Nhost hook named `useSignInEmailPassword` inside our
`SignIn` component the same way we did with our `SignUp` component. So, here's
what your component should look like after applying the changes for the sign-in
logic:
```jsx title="components/SignIn.js"
import styles from '../styles/components/SignIn.module.css'
import { useState } from 'react'
import { useRouter } from 'next/router'
import { useSignInEmailPassword } from '@nhost/nextjs'
import Link from 'next/link'
import Image from 'next/image'
import Input from './Input'
import Spinner from './Spinner'
const SignIn = () => {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const router = useRouter()
const { signInEmailPassword, isLoading, isSuccess, needsEmailVerification, isError, error } =
useSignInEmailPassword()
const handleOnSubmit = async (e) => {
e.preventDefault()
await signInEmailPassword(email, password)
}
if (isSuccess) {
router.push('/')
return null
}
const disableForm = isLoading || needsEmailVerification
return (
<div className={styles.container}>
<div className={styles.card}>
<div className={styles['logo-wrapper']}>
<Image src="/logo.svg" alt="logo" layout="fill" objectFit="contain" />
</div>
{needsEmailVerification ? (
<p className={styles['verification-text']}>
Please check your mailbox and follow the verification link to verify your email.
</p>
) : (
<>
<form onSubmit={handleOnSubmit} className={styles.form}>
<Input
type="email"
label="Email address"
value={email}
onChange={(e) => setEmail(e.target.value)}
disabled={disableForm}
required
/>
<Input
type="password"
label="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
disabled={disableForm}
required
/>
<button type="submit" disabled={disableForm} className={styles.button}>
{isLoading ? <Spinner size="sm" /> : 'Sign in'}
</button>
{isError ? <p className={styles['error-text']}>{error?.message}</p> : null}
</form>
</>
)}
</div>
<p className={styles.text}>
No account yet?{' '}
<Link href="/sign-up">
<a className={styles.link}>Sign up</a>
</Link>
</p>
</div>
)
}
export default SignIn
```
#### 3. Sign-out
Finally, to allow the users to sign out from the app, we can use the Nhost
`useSignOut` hook:
```jsx title="components/Layout.js"
import { useSignOut } from '@nhost/nextjs'
const Layout = ({ children = null }) => {
const { signOut } = useSignOut()
const menuItems = [
//..
{
label: 'Logout',
onClick: signOut,
icon: LogoutIcon
}
]
//...
}
```
### Protect routes
Now that we have implemented authentication, we can easily decide who can access
certain parts of our application.
In our case, we'll only allow authenticated users to have access to the `/` and
`/profile` routes. All the other users should be redirected to the `/sign-in`
page if they try to access those routes.
To do so, we can check the authentication status of the current user using the
Nhost SDK by creating a
[high-order component](https://reactjs.org/docs/higher-order-components.html):
```jsx title="withAuth.js"
import styles from './styles/pages/ProtectedRoute.module.css'
import { useRouter } from 'next/router'
import { useAuthenticationStatus } from '@nhost/nextjs'
import Spinner from './components/Spinner'
export default function withAuth(Component) {
return function AuthProtected(props) {
const router = useRouter()
const { isLoading, isAuthenticated } = useAuthenticationStatus()
if (isLoading) {
return (
<div className={styles.container}>
<Spinner />
</div>
)
}
if (!isAuthenticated) {
router.push('/sign-in')
return null
}
return <Component {...props} />
}
}
```
Then, wrap our Next.js pages, `index.js` and `profile.js`, with it:
<Tabs
defaultValue="index"
values={[
{label: 'pages/index.js', value: 'index'},
{label: 'pages/profile.js', value: 'profile'},
]}>
<TabItem value="index">
```js
import withAuth from '../withAuth'
const Home = () => {
//...
}
export default withAuth(Home)
```
</TabItem>
<TabItem value="profile">
```js
import withAuth from '../withAuth'
const Profile = () => {
//...
}
export default withAuth(Profile)
```
</TabItem>
</Tabs>
### Retrieve user data
Finally, let's display the information of the authenticated user throughout his
dashboard to make the app more personalized.
Getting the current authenticated user data is quite easy. We
can use the `useUserData` hook provided by Nhost to do it.
So, open the `UserProvider.js` file and use this hook like so:
```js
import React, { useContext } from 'react'
// highlight-next-line
import { useUserData } from '@nhost/nextjs'
const UserContext = React.createContext(null)
export function UserProvider({ children = null }) {
// highlight-next-line
const user = useUserData()
return <UserContext.Provider value={{ user }}>{children}</UserContext.Provider>
}
export function useUserContext() {
return useContext(UserContext)
}
```
That's it! The JSX code for rendering the user data (email, display name, etc.)
is already included in your components as part of the example repository you've
bootstrapped at the beginning of this guide.
### Update user data
Nhost provides a GraphQL API through Hasura so that we can query and mutate our
data instantly.
In this tutorial, we'll use the
[Apollo GraphQL client](https://www.apollographql.com/) for interacting with
this GraphQL API.
So, start by installing the following dependencies:
<Tabs groupId="package-manager">
<TabItem value="npm" label="npm" default>
```bash
npm install @nhost/react-apollo @apollo/client
```
</TabItem>
<TabItem value="yarn" label="Yarn">
```bash
yarn add @nhost/react-apollo @apollo/client
```
</TabItem>
</Tabs>
Then, add the `NhostApolloProvider` from `@nhost/react-apollo` into your
`_app_.js` file.
```jsx title="pages/_app.js"
import { NhostApolloProvider } from '@nhost/react-apollo'
function MyApp({ Component, pageProps }) {
return (
<NhostProvider nhost={nhost} initial={pageProps.nhostSession}>
<NhostApolloProvider nhost={nhost}>{/* ... */}</NhostApolloProvider>
</NhostProvider>
)
}
```
From there, we can construct our GraphQL query and use the Apollo `useMutation`
hook to execute that query when the user submits the form from the profile page:
```js title="pages/profile.js"
import { gql, useMutation } from '@apollo/client'
import { toast } from 'react-hot-toast'
const UPDATE_USER_MUTATION = gql`
mutation ($id: uuid!, $displayName: String!, $metadata: jsonb) {
updateUser(pk_columns: { id: $id }, _set: { displayName: $displayName, metadata: $metadata }) {
id
displayName
metadata
}
}
`
const Profile = () => {
const [mutateUser, { loading: updatingProfile }] = useMutation(UPDATE_USER_MUTATION)
const updateUserProfile = async (e) => {
e.preventDefault()
try {
await mutateUser({
variables: {
id: user.id,
displayName: `${firstName} ${lastName}`.trim(),
metadata: {
firstName,
lastName
}
}
})
toast.success('Updated successfully', { id: 'updateProfile' })
} catch (error) {
toast.error('Unable to update profile', { id: 'updateProfile' })
}
}
//...
}
```
Finally, since Hasura has an **allow nothing by default** policy, and we haven't
set any permissions yet, our GraphQL mutations would fail.
So, open the Hasura console from the **Data** tab of your project from [your Nhost dashboard](https://app.nhost.io/). Then, go to the **permissions** tab of the `users` table, type in `user` in the role
cell, and click the edit icon on the `select` operation:
![Hasura users permissions](/img/quickstarts/hasura-permissions-1.png)
To restrict the user to read his data only, specify a condition with the
user's ID and the `X-Hasura-User-ID` session variable, which is passed with each
requests.
![Hasura users permissions](/img/quickstarts/hasura-permissions-2.png)
Next, select the columns you'd like the users to have access to, and click
**Save Permissions**.
![Hasura users permissions](/img/quickstarts/hasura-permissions-3.png)
Repeat the same steps on the `update` operation for the `user` role to allow
users to update their `displayName` and `metadata` only.
Finally, to add caching, synchronizing, and updating server state in your Next.js app, let's refactor the user data fetching by using the Apollo client and our GraphQL API instead.
So, first add the following GraphQL query to retrieve the current user data from the `UserProvider.js` file:
```js title="UserProvider.js"
import { gql } from '@apollo/client'
const GET_USER_QUERY = gql`
query GetUser($id: uuid!) {
user(id: $id) {
id
email
displayName
metadata
avatarUrl
}
}
`
export function UserProvider() {
//...
}
```
Then, replace the `useUserData` hook with the `useUserId` hook to retrieve the current user's ID only.
```js title="UserProvider.js"
import { useUserId } from '@nhost/nextjs'
export function UserProvider() {
const id = useUserId()
//...
}
```
Finally, we can run our GraphQL query using the `useQuery` hook and the current user's ID.
```jsx title="UserProvider.js"
// highlight-next-line
import { gql, useQuery } from '@apollo/client'
export function UserProvider({ children = null }) {
const id = useUserId()
// highlight-start
const { loading, error, data } = useQuery(GET_USER_QUERY, {
variables: { id },
skip: !id
})
const user = data?.user
// highlight-end
// highlight-start
if (error) {
return <p>Something went wrong. Try to refresh the page.</p>
}
if (loading) {
return null
}
// highlight-end
return <UserContext.Provider value={{ user }}>{children}</UserContext.Provider>
}
```
You now have a fully functional Next.js application. Congratulations!
## Next Steps
- Did you enjoy Nhost? Give us a star ⭐ on [Github](https://github.com/nhost/nhost). Thank you!
- Check out our more in-depth [examples](https://github.com/nhost/nhost/tree/main/examples).
- Build your next app with [Nhost](https://app.nhost.io/)!

View File

@@ -1,682 +0,0 @@
---
title: 'Quickstart: React'
sidebar_label: React
sidebar_position: 1
image: /img/og/quickstart-react.png
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
# Quickstart: React
## Introduction
This quickstart guide provides the steps you need to build a simple React app
powered by Nhost for the backend. It includes:
- Database: [PostgreSQL](https://www.postgresql.org/)
- Instant GraphQL API: [Hasura](https://hasura.io/)
- Authentication: [Hasura Auth](https://github.com/nhost/hasura-auth/)
- Storage: [Hasura Storage](https://hub.docker.com/r/nhost/hasura-storage)
By the end of this guide, you'll have a full-stack app that allows users to log
in to access a protected dashboard and update their profile information.
## Prerequisites
Before getting started, let's make sure that your development environment is
ready.
You'll need **Node.js** version 14 or later: [install it from here](https://nodejs.org/en/).
## Project setup
### Create a new Nhost project
import CreateProject from '@site/src/components/create-nhost-project.mdx'
<CreateProject />
:::info
You can also connect your Nhost project to a Git repository at GitHub. When you do this, any updates you push to your code will automatically be deployed. [Learn more](/platform/git)
:::
## Initialize the app
### Create a React app
The simplest way to create a new React application is by using the tool called
`create-react-app`, which bootstraps a React app for you without the hassle of
configuring everything yourself.
So, open your terminal, and run the following command:
```bash
npx create-react-app my-nhost-app --template nhost-quickstart
```
:::info
This command uses an [existing template](https://github.com/nhost/cra-template-nhost-quickstart), through the `--template` flag, which already contains the React components and pages we'll use for this guide.
:::
You can now `cd` into your project directory:
```bash
cd my-nhost-app
```
And run the development server with the following command:
<Tabs groupId="package-manager">
<TabItem value="npm" label="npm" default>
```bash
npm start
```
</TabItem>
<TabItem value="yarn" label="Yarn">
```bash
yarn start
```
</TabItem>
</Tabs>
If everything is working fine, your React development server should be running
on port 3000. Open [http://localhost:3000](http://localhost:3000) from your
browser to check this out.
### Configure Nhost with React
To work with Nhost from within our React app, we'll use the
[React SDK](https://github.com/nhost/nhost/tree/main/packages/react) provided
by Nhost. It's a wrapper around the
[Nhost JavaScript SDK](https://github.com/nhost/nhost/tree/main/packages/nhost-js) which
gives us a way to interact with our Nhost backend using React hooks.
You can install the Nhost React SDK with:
<Tabs groupId="package-manager">
<TabItem value="npm" label="npm" default>
```bash
npm install @nhost/react graphql
```
</TabItem>
<TabItem value="yarn" label="Yarn">
```bash
yarn add @nhost/react graphql
```
</TabItem>
</Tabs>
Next, open your `App.js` file as we'll now configure Nhost inside our app.
The Nhost React SDK comes with a React provider named `NhostProvider` that
makes the authentication state and all the provided React hooks available in our
application.
Use the following code to instantiate a new Nhost client and link it to your
Nhost backend:
```jsx title="src/App.js"
import { NhostClient, NhostProvider } from '@nhost/react'
const nhost = new NhostClient({
subdomain: process.env.REACT_APP_NHOST_SUBDOMAIN,
region: process.env.REACT_APP_NHOST_REGION
})
function App() {
return (
<NhostProvider nhost={nhost}>
<BrowserRouter>{/* ... */}</BrowserRouter>
</NhostProvider>
)
}
export default App
```
Finally, make sure to create an environment variable named
`REACT_APP_NHOST_SUBDOMAIN` and `REACT_APP_NHOST_REGION` to store your Nhost domain details:
```yaml title=".env.local"
REACT_APP_NHOST_SUBDOMAIN=[subdomain]
REACT_APP_NHOST_REGION=[region]
```
You find your Nhost project's `subdomain` and `region` in the [project overview](https://app.nhost.io):
![Project Overview](/img/quickstarts/app-dashboard.png)
:::caution
Don't forget to restart your React server after saving your `.env.local`
file to load your new environment variable.
:::
:::info Nhost CLI
Do you use the Nhost CLI? Learn how to set `subdomain` and `region` in the [CLI documentation](/cli#subdomain-and-region).
:::
## Build the app
### Add authentication
#### 1. Sign-up
The next step is to allow our users to authenticate into our application.
Let's start with implementing the sign-up process.
For that, we'll use the `useSignUpEmailPassword` hook provided by the Nhost
React SDK within our `SignUp` component.
So, open up the corresponding file from your project, and use the following
code:
```jsx title="src/components/SignUp.js"
import styles from '../styles/components/SignUp.module.css'
import { useState } from 'react'
import { useSignUpEmailPassword } from '@nhost/react'
import { Link, Navigate } from 'react-router-dom'
import Input from './Input'
import Spinner from './Spinner'
const SignUp = () => {
const [firstName, setFirstName] = useState('')
const [lastName, setLastName] = useState('')
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const { signUpEmailPassword, isLoading, isSuccess, needsEmailVerification, isError, error } =
useSignUpEmailPassword()
const handleOnSubmit = (e) => {
e.preventDefault()
signUpEmailPassword(email, password, {
displayName: `${firstName} ${lastName}`.trim(),
metadata: {
firstName,
lastName
}
})
}
if (isSuccess) {
return <Navigate to="/" replace={true} />
}
const disableForm = isLoading || needsEmailVerification
return (
<div className={styles.container}>
<div className={styles.card}>
<div className={styles['logo-wrapper']}>
<img src={process.env.PUBLIC_URL + 'logo.svg'} alt="logo" />
</div>
{needsEmailVerification ? (
<p className={styles['verification-text']}>
Please check your mailbox and follow the verification link to verify your email.
</p>
) : (
<form onSubmit={handleOnSubmit} className={styles.form}>
<div className={styles['input-group']}>
<Input
label="First name"
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
disabled={disableForm}
required
/>
<Input
label="Last name"
value={lastName}
onChange={(e) => setLastName(e.target.value)}
disabled={disableForm}
required
/>
</div>
<Input
type="email"
label="Email address"
value={email}
onChange={(e) => setEmail(e.target.value)}
disabled={disableForm}
required
/>
<Input
type="password"
label="Create password"
value={password}
onChange={(e) => setPassword(e.target.value)}
disabled={disableForm}
required
/>
<button type="submit" disabled={disableForm} className={styles.button}>
{isLoading ? <Spinner size="sm" /> : 'Create account'}
</button>
{isError ? <p className={styles['error-text']}>{error?.message}</p> : null}
</form>
)}
</div>
<p className={styles.text}>
Already have an account?{' '}
<Link to="/sign-in" className={styles.link}>
Sign in
</Link>
</p>
</div>
)
}
export default SignUp
```
By default, the user must verify his email address before fully signing up. You can change this setting from your Nhost dashboard.
#### 2. Sign-in
Now that new users can sign up for our application, let's see how to allow
existing users to sign in with email and password.
For that, we will use the Nhost hook named `useSignInEmailPassword` inside our
`SignIn` component the same way we did with our `SignUp` component. So, here's
what your component should look like after applying the changes for the sign-in
logic:
```jsx title="src/components/SignIn.js"
import styles from '../styles/components/SignIn.module.css'
import { useState } from 'react'
import { useSignInEmailPassword } from '@nhost/react'
import { Link, Navigate } from 'react-router-dom'
import Input from './Input'
import Spinner from './Spinner'
const SignIn = () => {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const { signInEmailPassword, isLoading, isSuccess, needsEmailVerification, isError, error } =
useSignInEmailPassword()
const handleOnSubmit = (e) => {
e.preventDefault()
signInEmailPassword(email, password)
}
if (isSuccess) {
return <Navigate to="/" replace={true} />
}
const disableForm = isLoading || needsEmailVerification
return (
<div className={styles.container}>
<div className={styles.card}>
<div className={styles['logo-wrapper']}>
<img src={process.env.PUBLIC_URL + 'logo.svg'} alt="logo" />
</div>
{needsEmailVerification ? (
<p className={styles['verification-text']}>
Please check your mailbox and follow the verification link to verify your email.
</p>
) : (
<form onSubmit={handleOnSubmit} className={styles.form}>
<Input
type="email"
label="Email address"
value={email}
onChange={(e) => setEmail(e.target.value)}
disabled={disableForm}
required
/>
<Input
type="password"
label="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
disabled={disableForm}
required
/>
<button type="submit" disabled={disableForm} className={styles.button}>
{isLoading ? <Spinner size="sm" /> : 'Sign in'}
</button>
{isError ? <p className={styles['error-text']}>{error?.message}</p> : null}
</form>
)}
</div>
<p className={styles.text}>
No account yet?{' '}
<Link to="/sign-up" className={styles.link}>
Sign up
</Link>
</p>
</div>
)
}
export default SignIn
```
#### 3. Sign-out
Finally, to allow the users to sign out from the app, we can use the Nhost
`useSignOut` hook:
```jsx title="src/components/Layout.js"
import { useSignOut } from '@nhost/react'
const Layout = () => {
const { signOut } = useSignOut()
const menuItems = [
//..
{
label: 'Logout',
onClick: signOut,
icon: LogoutIcon
}
]
//...
}
```
### Protect routes
Now that we have implemented authentication, we can easily decide who can access
certain parts of our application.
In our case, we'll only allow authenticated users to have access to the `/` and
`/profile` routes. All the other users should be redirected to the `/sign-in`
page if they try to access those routes.
To do so, we can create a wrapper component (`ProtectedRoute`) to check the authentication status of the current user using the Nhost SDK:
```jsx title="src/components/ProtectedRoute.js"
import styles from '../styles/components/ProtectedRoute.module.css'
import { useAuthenticationStatus } from '@nhost/react'
import { Navigate, useLocation } from 'react-router-dom'
import Spinner from './Spinner'
const ProtectedRoute = ({ children }) => {
const { isAuthenticated, isLoading } = useAuthenticationStatus()
const location = useLocation()
if (isLoading) {
return (
<div className={styles.container}>
<Spinner />
</div>
)
}
if (!isAuthenticated) {
return <Navigate to="/sign-in" state={{ from: location }} replace />
}
return children
}
export default ProtectedRoute
```
Then, we can use a [layout route](https://reactrouter.com/docs/en/v6/getting-started/concepts#layout-routes) in our `App.js` file, to wrap the `ProtectedRoute` component around the routes we want to protect:
```jsx title="src/App.js"
import ProtectedRoute from './components/ProtectedRoute'
function App() {
return (
<NhostProvider nhost={nhost}>
<BrowserRouter>
<Routes>
<Route path="sign-up" element={<SignUp />} />
<Route path="sign-in" element={<SignIn />} />
<Route
path="/"
// highlight-start
element={
<ProtectedRoute>
<Layout />
</ProtectedRoute>
}
// highlight-end
>
<Route index element={<Dashboard />} />
<Route path="profile" element={<Profile />} />
</Route>
</Routes>
</BrowserRouter>
</NhostProvider>
)
}
```
### Retrieve user data
Finally, let's display the information of the authenticated user throughout his
dashboard to make the app more personalized.
Getting the current authenticated user data is quite easy. Indeed, we
can use the `useUserData` hook provided by Nhost to do it.
So, open the `components/Layout.js` file and use this hook like so:
```js
import { useUserData } from '@nhost/react'
const Layout = () => {
const user = useUserData()
//...
}
```
That's it! The JSX code for rendering the user data (email, display name, etc.)
is already included in your components as part of the template you've
bootstraped at the beginning of this guide.
### Update user data
Nhost provides a GraphQL API through Hasura so that we can query and mutate our
data instantly.
In this tutorial, we'll use the
[Apollo GraphQL client](https://www.apollographql.com/) for interacting with
this GraphQL API.
So, start by installing the following dependencies:
<Tabs groupId="package-manager">
<TabItem value="npm" label="npm" default>
```bash
npm install @nhost/react-apollo @apollo/client
```
</TabItem>
<TabItem value="yarn" label="Yarn">
```bash
yarn add @nhost/react-apollo @apollo/client
```
</TabItem>
</Tabs>
Then, add the `NhostApolloProvider` from `@nhost/react-apollo` into your
`App.js` file.
```jsx title="src/App.js"
import { NhostApolloProvider } from '@nhost/react-apollo'
function App() {
return (
<NhostProvider nhost={nhost}>
<NhostApolloProvider nhost={nhost}>{/* ... */}</NhostApolloProvider>
</NhostProvider>
)
}
```
From there, we can construct our GraphQL query and use the Apollo `useMutation`
hook to execute that query when the user submits the form from the profile page:
```js title="src/pages/Profile.js"
import { gql, useMutation } from '@apollo/client'
import { toast } from 'react-hot-toast'
const UPDATE_USER_MUTATION = gql`
mutation ($id: uuid!, $displayName: String!, $metadata: jsonb) {
updateUser(pk_columns: { id: $id }, _set: { displayName: $displayName, metadata: $metadata }) {
id
displayName
metadata
}
}
`
const Profile = () => {
const [mutateUser, { loading: updatingProfile }] = useMutation(UPDATE_USER_MUTATION)
const updateUserProfile = async (e) => {
e.preventDefault()
try {
await mutateUser({
variables: {
id: user.id,
displayName: `${firstName} ${lastName}`.trim(),
metadata: {
firstName,
lastName
}
}
})
toast.success('Updated successfully', { id: 'updateProfile' })
} catch (error) {
toast.error('Unable to update profile', { id: 'updateProfile' })
}
}
//...
}
```
Finally, since Hasura has an **allow nothing by default** policy, and we haven't
set any permissions yet, our GraphQL mutations would fail.
So, open the Hasura console from the **Data** tab of your project from [your Nhost dashboard](https://app.nhost.io/). Then, go to the **permissions** tab of the `users` table, type in `user` in the role
cell, and click the edit icon on the `select` operation:
![Hasura users permissions](/img/quickstarts/hasura-permissions-1.png)
To restrict the user to read his own data only, specify a condition with the
user's ID and the `X-Hasura-User-ID` session variable, which is passed with each
requests.
![Hasura users permissions](/img/quickstarts/hasura-permissions-2.png)
Next, select the columns you'd like the users to have access to, and click
**Save Permissions**.
![Hasura users permissions](/img/quickstarts/hasura-permissions-3.png)
Repeat the same steps on the `update` operation for the `user` role to allow
users to update their `displayName` and `metadata` only.
Finally, to add caching, synchronizing, and updating server state in your React app, let's refactor the user data fetching using the Apollo client and our GraphQL API instead.
So, first add the following GraphQL query to retrieve the current user data from the `Layout` component:
```js title="src/components/Layout.js"
import { gql } from '@apollo/client'
const GET_USER_QUERY = gql`
query GetUser($id: uuid!) {
user(id: $id) {
id
email
displayName
metadata
avatarUrl
}
}
`
const Layout = () => {
//...
}
```
Then, replace the `useUserData` hook with the `useUserId` hook to retrieve the current user's ID.
```js title="src/components/Layout.js"
import { useUserId } from '@nhost/react'
const Layout = () => {
const id = useUserId()
//...
}
```
Finally, we can run our GraphQL query using the `useQuery` hook and the current user's ID.
```jsx title="src/components/Layout.js"
// highlight-next-line
import { gql, useQuery } from '@apollo/client'
const Layout = () => {
const id = useUserId()
// highlight-start
const { loading, error, data } = useQuery(GET_USER_QUERY, {
variables: { id },
skip: !id
})
const user = data?.user
// highlight-end
//...
return (
<div>
<header>{/* ... */}</header>
<main className={styles.main}>
<div className={styles['main-container']}>
{/* highlight-start */}
{error ? (
<p>Something went wrong. Try to refresh the page.</p>
) : !loading ? (
<Outlet context={{ user }} />
) : null}
{/* highlight-end */}
</div>
</main>
</div>
)
}
```
You now have a fully functional React application. Congratulations!
## Next Steps
- Did you enjoy Nhost? Give us a star ⭐ on [Github](https://github.com/nhost/nhost). Thank you!
- Check out our more in-depth [examples](https://github.com/nhost/nhost/tree/main/examples).
- Build your next app with [Nhost](https://app.nhost.io/)!

File diff suppressed because it is too large Load Diff

View File

@@ -1,838 +0,0 @@
---
title: 'Quickstart: Vue'
sidebar_label: Vue
sidebar_position: 3
image: /img/og/quickstart-vue.png
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
# Quickstart: Vue
## Introduction
This quickstart guide provides the steps you need to build a simple Vue app
powered by Nhost for the backend. It includes:
- Database: [PostgreSQL](https://www.postgresql.org/)
- Instant GraphQL API: [Hasura](https://hasura.io/)
- Authentication: [Hasura Auth](https://github.com/nhost/hasura-auth/)
- Storage: [Hasura Storage](https://hub.docker.com/r/nhost/hasura-storage)
By the end of this guide, you'll have a full-stack app that allows users to log
in to access a protected dashboard and update their profile information.
:::tip
You can see the result of this quickstart [in our main repository](https://github.com/nhost/nhost/tree/main/examples/vue-quickstart).
You can also preview it in the browser: [![StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/nhost/nhost/tree/main/examples/vue-quickstart)
:::
## Prerequisites
Before getting started, let's make sure that your development environment is
ready.
You'll need **Node.js** version 14 or later: [install it from here](https://nodejs.org/en/).
## Project setup
### Create a new Nhost app
import CreateApp from '@site/src/components/create-nhost-project.mdx'
<CreateApp />
:::info
You can also connect your Nhost project to a Git repository at GitHub. When you do this, any updates you push to your code will automatically be deployed. [Learn more](/platform/git)
:::
## Initialize the app
### Create a Vue app
We will use a simple adaptation of [Vitesse Lite](https://github.com/antfu/vitesse-lite), a ready-to-deploy Vite template by Anthony Fu. We can scaffold it with [degit](https://github.com/Rich-Harris/degit).
Open your terminal, and run the following command:
```bash
npx degit nhost/vue-quickstart my-nhost-app
```
<br />
:::note Sidebar
A completed quickstart example can be be found in the [examples](https://github.com/nhost/nhost/tree/main/examples/vue-quickstart) directory.
Run the following command in your terminal to create a local copy the example project:
```bash
npx degit nhost/nhost/examples/vue-quickstart my-nhost-app
````
:::
You can now go into your project directory, install dependencies, and start the development server:
<Tabs groupId="package-manager">
<TabItem value="npm" label="npm" default>
```bash
cd my-nhost-app
npm install
npm dev
```
</TabItem>
<TabItem value="yarn" label="Yarn">
```bash
cd my-nhost-app
yarn
yarn dev
```
</TabItem>
<TabItem value="pnpm" label="pnpm">
```bash
cd my-nhost-app
pnpm install
pnpm dev
```
</TabItem>
</Tabs>
If everything is working fine, your Vue development server should be running
on port 3000. Open [http://localhost:3000](http://localhost:3000) from your
browser to check this out.
### Configure Nhost with Vue
To work with Nhost from within our Vue app, we'll use the
[Vue SDK](https://github.com/nhost/nhost/tree/main/packages/vue) provided
by Nhost. It's a wrapper around the
[Nhost JavaScript SDK](https://github.com/nhost/nhost/tree/main/packages/nhost-js) which
gives us a way to interact with our Nhost backend using Vue composables.
You can install the Nhost Vue SDK with:
<Tabs groupId="package-manager">
<TabItem value="npm" label="npm" default>
```bash
npm install @nhost/vue graphql
```
</TabItem>
<TabItem value="yarn" label="Yarn">
```bash
yarn add @nhost/vue graphql
```
</TabItem>
<TabItem value="pnpm" label="pnpm">
```bash
pnpm add @nhost/vue graphql
```
</TabItem>
</Tabs>
Next, open your `src/main.ts` file as we'll now configure Nhost inside our app.
The Nhost Vue SDK comes with a `NhostClient` that can be loaded into the Vue application as a plugin.
It makes the authentication state and all the provided Vue composables available in our
application.
Ensure the hightlighted code below is present to instantiate a new Nhost client
and link it to your Nhost backend:
```tsx title="src/main.ts"
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import routes from 'virtual:generated-pages'
// highlight-start
import { NhostClient } from '@nhost/vue'
// highlight-end
import App from './App.vue'
import '@unocss/reset/tailwind.css'
import './styles/main.css'
import 'uno.css'
// highlight-start
const nhost = new NhostClient({
subdomain: import.meta.env.VITE_NHOST_SUBDOMAIN
region: import.meta.env.VITE_NHOST_REGION
})
// highlight-end
const app = createApp(App)
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes
})
app
.use(router)
// highlight-start
.use(nhost)
// highlight-end
.mount('#app')
```
Finally, rename the existing `.env.example` file as `.env` or `.env.development` and store
the environment variables for `subdomain` and `region` in the file like this:
```yaml title=".env.development"
VITE_NHOST_SUBDOMAIN=[subdomain]
VITE_NHOST_REGION=[region]
```
You find your Nhost project's `subdomain` and `region` in the [project overview](https://app.nhost.io):
![Project Overview](/img/quickstarts/app-dashboard.png)
:::info Nhost CLI
Do you use the Nhost CLI? Learn how to set `subdomain` and `region` in the [CLI documentation](/cli#subdomain-and-region).
:::
## Build the app
### Add authentication
#### 1. Sign-up
The next step is to allow our users to authenticate into our application.
Let's start with implementing the sign-up process.
For that, we'll use the `useSignUpEmailPassword` composable provided by the Nhost
Vue SDK within a `/sign-up` page.
Let's create a new page in your project using the following code:
```markup title="src/pages/sign-up.vue"
<script setup lang="ts">
import { ref } from 'vue'
import { useSignUpEmailPassword } from '@nhost/vue'
import { useRouter } from 'vue-router'
const { signUpEmailPassword, needsEmailVerification } = useSignUpEmailPassword()
const router = useRouter()
const firstName = ref('')
const lastName = ref('')
const email = ref('')
const password = ref('')
const handleSubmit = async (event: Event) => {
event.preventDefault()
const { isSuccess } = await signUpEmailPassword(email, password, {
metadata: { firstName, lastName }
})
if (isSuccess)
router.push('/')
}
</script>
<template>
<p v-if="needsEmailVerification">
Please check your mailbox and follow the verification link to verify your email.
</p>
<form v-else @submit="handleSubmit">
<input v-model="firstName" placeholder="First name" class="input" />
<br />
<input v-model="lastName" placeholder="Last name" class="input" />
<br />
<input v-model="email" type="email" placeholder="Email" class="input" />
<br />
<input v-model="password" type="password" placeholder="Password" class="input" />
<br />
<button class="btn-submit" type="submit">
Sign up
</button>
</form>
</template>
```
#### 2. Sign-in
Now that new users can sign up for our application, let's see how to allow
existing users to sign in with email and password.
For that, we will use the Nhost composable named `useSignInEmailPassword` inside a new
`sign-in` page the same way we did with our `sign-up` page.
Let's create a `src/pages/sign-in.vue` component:
```markup title="src/pages/sign-in.vue"
<script setup lang="ts">
import { ref } from 'vue'
import { useSignInEmailPassword } from '@nhost/vue'
import { useRouter } from 'vue-router'
const { signInEmailPassword, needsEmailVerification } = useSignInEmailPassword()
const router = useRouter()
const email = ref('')
const password = ref('')
const handleSubmit = async (event: Event) => {
event.preventDefault()
const { isSuccess } = await signInEmailPassword(email, password)
if (isSuccess)
router.push('/')
}
</script>
<template>
<p v-if="needsEmailVerification">
Your email is not yet verified. Please check your mailbox and
follow the verification link to finish registration.
</p>
<form v-else @submit="handleSubmit">
<input v-model="email" type="email" placeholder="Email" class="input" />
<br />
<input v-model="password" type="password" placeholder="Password" class="input" />
<br />
<button class="btn-submit" type="submit">
Sign in
</button>
</form>
</template>
```
#### 3. Home page
Let's also add links to sign up and sign in in our index page.
```markup title="src/pages/index.vue"
<template>
<div>
<div i-carbon-home text-4xl inline-block />
<p>Nhost with Vue</p>
<p>
<em text-sm op75>Quickstart</em>
</p>
<div py-4 />
<!-- highlight-start -->
<router-link class="btn" to="/sign-up">
Sign Up
</router-link>
<br />
<router-link class="btn" to="/sign-in">
Sign In
</router-link>
<!-- highlight-end -->
</div>
</template>
```
#### 4. Sign-out
Finally, to allow the users to sign out from the app, we can use the Nhost `useSignOut` composable.
We'll also use `useAuthenticationStatus` to show the button only when the user is authenticated:
```markup title="src/components/Footer.vue"
<script setup lang="ts">
import { useRouter } from 'vue-router'
import { useAuthenticated, useSignOut } from '@nhost/vue'
import { isDark, toggleDark } from '~/composables'
const isAuthenticated = useAuthenticated()
const { signOut } = useSignOut()
const router = useRouter()
const handleSignOut = () => {
signOut()
router.push('/')
}
</script>
<template>
<nav text-xl mt-6 inline-flex gap-2>
<button class="icon-btn !outline-none" @click="toggleDark()">
<div v-if="isDark" i-carbon-moon />
<div v-else i-carbon-sun />
</button>
<button v-if="isAuthenticated" class="icon-btn !outline-none" @click="handleSignOut">
<div i-carbon-logout />
</button>
</nav>
</template>
```
### Protect routes
Now that we have implemented authentication, we can easily decide who can access
certain parts of our application.
Let's create a profile page that will be only accessible to authenticated users.
If an unauthenticated user attempts to load it, it will redirect them to the `/sign-up` page:
```markup title="src/pages/profile.vue"
<template>
<div>
<div i-carbon-home text-4xl inline-block />
<p>Profile page</p>
</div>
</template>
```
Then, we can use a [beforeEach navigation guard](https://router.vuejs.org/guide/advanced/navigation-guards.html#global-before-guards) in our `main.ts` file:
```tsx title="src/main.ts"
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import routes from 'virtual:generated-pages'
import { NhostClient } from '@nhost/vue'
import App from './App.vue'
import '@unocss/reset/tailwind.css'
import './styles/main.css'
import 'uno.css'
const nhost = new NhostClient({
subdomain: import.meta.env.VITE_NHOST_SUBDOMAIN,
region: import.meta.env.VITE_NHOST_REGION
})
const app = createApp(App)
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes
})
// highlight-start
router.beforeEach(async (to) => {
if (to.path === '/profile' && !(await nhost.auth.isAuthenticatedAsync())) {
return '/sign-in'
}
return true
})
// highlight-end
app
.use(router)
.use(nhost)
.mount('#app')
```
Add a link to the profile page in the index page `/`:
```markup title="src/pages/index.vue"
<template>
<div>
<div i-carbon-home text-4xl inline-block />
<p>Nhost with Vue</p>
<p>
<em text-sm op75>Quickstart</em>
</p>
<div py-4 />
<!-- highlight-start -->
<router-link class="btn" to="/profile">
Profile
</router-link>
<br />
<!-- highlight-end -->
<router-link class="btn" to="/sign-up">
Sign Up
</router-link>
<br />
<router-link class="btn" to="/sign-in">
Sign In
</router-link>
</div>
</template>
```
### Retrieve user data
Finally, let's display the information of the authenticated user throughout their
dashboard to make the app more personalized.
Getting the current authenticated user data is quite easy. Indeed, we can
use the `useUserData` composable provided by Nhost to do it.
When the user is authenticated, it returns the information fetched from the `users` table,
such as the display name, the email, or the user's roles. This composable returns `null`
until the user is effectively authenticated.
Let's update the profile page to use it:
```markup title="src/pages/profile.vue"
<!-- highlight-start -->
<script setup lang="ts">
import { useUserData } from '@nhost/vue'
const user = useUserData()
</script>
<!-- highlight-end -->
<template>
<div>
<div i-carbon-home text-4xl inline-block />
<p>Profile page</p>
<!-- highlight-start -->
<p>
<em text-sm op75>Quickstart</em>
</p>
<div v-if="user" py-4>
<p my-4>
Hello, {{ user?.displayName }}. Your email is {{ user?.email }}.
</p>
</div>
<!-- highlight-end -->
</div>
</template>
```
### Update user data
Nhost provides a GraphQL API through Hasura so that we can query and mutate our data instantly.
In this tutorial, we'll use [Vue Apollo v4](https://v4.apollo.vuejs.org) for interacting with
this GraphQL API. Nhost comes with a custom Apollo client that syncs the Apollo client with
the authentication status of your users.
So, we start by installing the following dependencies:
<Tabs groupId="package-manager">
<TabItem value="npm" label="npm" default>
```bash
npm install @nhost/apollo @apollo/client graphql graphql-tag @vue/apollo-composable
```
</TabItem>
<TabItem value="yarn" label="Yarn">
```bash
yarn add @nhost/apollo @apollo/client graphql graphql-tag @vue/apollo-composable
```
</TabItem>
<TabItem value="pnpm" label="pnpm">
```bash
pnpm add @nhost/apollo @apollo/client graphql graphql-tag @vue/apollo-composable
```
</TabItem>
</Tabs>
Then, create the Apollo client in your `src/main.ts` file, and provide it to your Vue app:
```tsx title="src/main.ts"
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import routes from 'virtual:generated-pages'
import { NhostClient } from '@nhost/vue'
// highlight-start
import { createApolloClient } from '@nhost/apollo'
import { DefaultApolloClient } from '@vue/apollo-composable'
// highlight-end
import App from './App.vue'
import '@unocss/reset/tailwind.css'
import './styles/main.css'
import 'uno.css'
const nhost = new NhostClient({
subdomain: import.meta.env.VITE_NHOST_SUBDOMAIN,
region: import.meta.env.VITE_NHOST_REGION
})
const app = createApp(App)
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes
})
router.beforeEach(async (to) => {
if (to.path === '/profile' && !(await nhost.auth.isAuthenticatedAsync())) {
return '/sign-in'
}
return true
})
// highlight-start
const apolloClient = createApolloClient({ nhost })
// highlight-end
app
.use(router)
.use(nhost)
// highlight-start
.provide(DefaultApolloClient, apolloClient)
// highlight-end
.mount('#app')
```
From there, we can construct our GraphQL query and use the Apollo `useMutation`
composable to execute that query when the user submits the form from the profile page:
```markup title="src/pages/profile.vue"
<script setup lang="ts">
// highlight-start
import { gql } from '@apollo/client/core'
import { useNhostClient, useUserData } from '@nhost/vue'
import { useMutation } from '@vue/apollo-composable'
import { ref } from 'vue'
// highlight-end
const user = useUserData()
// highlight-start
const { nhost } = useNhostClient()
const UPDATE_USER_MUTATION = gql`
mutation ($id: uuid!, $displayName: String!, $metadata: jsonb) {
updateUser(
pk_columns: { id: $id }
_set: { displayName: $displayName, metadata: $metadata }
) {
id
displayName
metadata
}
}
`
const firstName = ref('')
const lastName = ref('')
const { mutate, loading, error } = useMutation(UPDATE_USER_MUTATION)
const updateUserProfile = async (event: Event) => {
event.preventDefault()
if (user.value) {
await mutate({
id: user.value.id,
displayName: `${firstName.value} ${lastName.value}`.trim(),
metadata: {
firstName: firstName.value,
lastName: lastName.value
}
})
await nhost.auth.refreshSession()
}
}
// highlight-end
</script>
<template>
<div>
<div i-carbon-home text-4xl inline-block />
<p>Profile page</p>
<p>
<em text-sm op75>Quickstart</em>
</p>
<div v-if="user" py-4>
<p my-4>
Hello, {{ user?.displayName }}. Your email is {{ user?.email }}.
</p>
<!-- highlight-start -->
<form @submit="updateUserProfile">
<input v-model="firstName" placeholder="First name" class="input" />
<br />
<input v-model="lastName" placeholder="Last name" class="input" />
<br />
<button className="m-3 text-sm btn" :disabled="loading">
Save
</button>
<div v-if="error">
{{ error.message }}
</div>
</form>
<!-- highlight-end -->
</div>
</div>
</template>
```
:::tip
You probably have noticed that we are calling `nhost.auth.refreshSession()` after we updated the
user using the GraphQL mutation. The Nhost client only extracts user information from the
access token (JWT), that is kept in memory and refreshed every 15 minutes. As user information
has been updated, we force an access token refresh so it is kept up to date.
:::
Finally, since Hasura has an **allow nothing by default** policy, and we haven't
set any permissions yet, our GraphQL mutations would fail.
So, open the Hasura console from the **Data** tab of your project from [your Nhost dashboard](https://app.nhost.io/). Then, go to the **permissions** tab of the `users` table, type in `user` in the role cell,
and click the edit icon on the `select` operation:
![Hasura users permissions](/img/quickstarts/hasura-permissions-1.png)
To restrict the user to read his own data only, specify a condition with the user's ID and
the `X-Hasura-User-ID` session variable, which is passed with each requests.
![Hasura users permissions](/img/quickstarts/hasura-permissions-2.png)
Next, select the columns you'd like the users to have access to, and click
**Save Permissions**.
![Hasura users permissions](/img/quickstarts/hasura-permissions-3.png)
:::tip Important
Repeat the same steps on the `update` operation for the `user` role to allow
users to update their `displayName` and `metadata` only.
:::
### Adding Real-time synchronizing
To add real-time caching, synchronizing, and updating server state in your Vue app,
let's refactor the user data fetching using the Apollo client and our GraphQL API instead.
First, update the profile page, and replace the `useUserData` composable with the `useUserId`
composable to retrieve the current user's ID.
```ts title="src/pages/profile.vue"
// replace `import { useNhostClient, useUserData } from '@nhost/vue'`
import { useNhostClient, useUserId } from '@nhost/vue'
// replace `const user = useUserData()`
const id = useUserId()
```
Then import the `computed()` function from Vue.
```ts title="src/pages/profile.vue"
// replace `import { ref } from 'vue'`
import { computed, ref } from 'vue'
```
Then and add the highlighted code below to add a GraphQL subscription that retrieves the
current user data component:
```ts title="src/pages/profile.vue"
// snip...
const { nhost } = useNhostClient()
// highlight-start
const GET_USER_SUBSCRIPTION = gql`
subscription GetUser($id: uuid!) {
user(id: $id) {
id
email
displayName
metadata
avatarUrl
}
}
`
// highlight-end
const id = useUserId()
// highlight-start
const { result } = useSubscription(
GET_USER_SUBSCRIPTION,
computed(() => ({ id: id.value }))
)
const user = computed(() => result.value?.user)
// highlight-end
// snip...
```
We can now run our GraphQL subscription using the `useSubscription` composable and retrieve
the current user's ID.
The completed `profile.vue` page should look like this:
```markup title="src/pages/profile.vue"
<script setup lang="ts">
import { gql } from '@apollo/client/core'
import { useNhostClient, useUserId } from '@nhost/vue'
import { useMutation, useSubscription } from '@vue/apollo-composable'
import { computed, ref } from 'vue'
const { nhost } = useNhostClient()
const GET_USER_SUBSCRIPTION = gql`
subscription GetUser($id: uuid!) {
user(id: $id) {
id
email
displayName
metadata
avatarUrl
}
}
`
const id = useUserId()
const { result } = useSubscription(
GET_USER_SUBSCRIPTION,
computed(() => ({ id: id.value }))
)
const user = computed(() => result.value?.user)
const UPDATE_USER_MUTATION = gql`
mutation ($id: uuid!, $displayName: String!, $metadata: jsonb) {
updateUser(
pk_columns: { id: $id }
_set: { displayName: $displayName, metadata: $metadata }
) {
id
displayName
metadata
}
}
`
const firstName = ref('')
const lastName = ref('')
const { mutate, loading, error } = useMutation(UPDATE_USER_MUTATION)
const updateUserProfile = async (event: Event) => {
event.preventDefault()
if (user.value) {
await mutate({
id: user.value.id,
displayName: `${firstName.value} ${lastName.value}`.trim(),
metadata: {
firstName: firstName.value,
lastName: lastName.value
}
})
await nhost.auth.refreshSession()
}
}
</script>
<template>
<div>
<div i-carbon-home text-4xl inline-block />
<p>Profile page</p>
<p>
<em text-sm op75>Quickstart</em>
</p>
<div v-if="user" py-4>
<p my-4>
Hello, {{ user.displayName }}. Your email is {{ user.email }}.
</p>
<form @submit="updateUserProfile">
<input v-model="firstName" placeholder="First name" class="input" />
<br />
<input v-model="lastName" placeholder="Last name" class="input" />
<br />
<button className="m-3 text-sm btn" :disabled="loading">
Save
</button>
<div v-if="error">
{{ error.message }}
</div>
</form>
</div>
</div>
</template>
```
You now have a fully functional Vue application. Congratulations!
## Next Steps
- Did you enjoy Nhost? Give us a star ⭐ on [Github](https://github.com/nhost/nhost). Thank you!
- Check out our more in-depth [examples](https://github.com/nhost/nhost/tree/main/examples).
- Build your next app with [Nhost](https://app.nhost.io/)!

View File

@@ -1,4 +0,0 @@
{
"label": "Reference",
"link": { "type": "generated-index" }
}

View File

@@ -1,32 +0,0 @@
---
title: 'Reference'
sidebar_position: 0
---
In this section:
### Nhost JavaScript SDK
- [Overview](/reference/javascript)
- [Authentication](/reference/javascript/auth)
- [Storage](/reference/javascript/storage)
- [Functions](/reference/javascript/nhost-js/functions)
- [GraphQL](/reference/javascript/graphql)
### React
- [Getting started](/reference/react)
- [Protecting routes](/reference/react/protecting-routes)
- [Apollo GraphQL](/reference/react/apollo)
### Next.js
- [Introduction](/reference/nextjs)
- [Protecting routes](/reference/nextjs/protecting-routes)
- [Architecture](/reference/nextjs/architecture)
### Vue
- [Getting started](/reference/vue)
- [Protecting routes](/reference/vue/protecting-routes)
- [Apollo GraphQL](/reference/vue/apollo)

View File

@@ -1,85 +0,0 @@
---
title: 'JavaScript'
sidebar_position: 1
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
The Nhost JavaScript client is the primary way of interacting with your Nhost project. It exposes a standard interface for each of the following services:
- [Authentication](/reference/javascript/auth)
- [Storage](/reference/javascript/storage)
- [Functions](/reference/javascript/nhost-js/functions)
- [GraphQL](/reference/javascript/graphql)
## Installation
Install the Nhost client together with GraphQL:
<Tabs groupId="package-manager">
<TabItem value="npm" label="npm" default>
```bash
npm install @nhost/nhost-js graphql
```
</TabItem>
<TabItem value="yarn" label="Yarn">
```bash
yarn add @nhost/nhost-js graphql
```
</TabItem>
</Tabs>
## Initializing
Initialize a single `nhost` instance using your Nhost `subdomain` and `region`:
```ts title=src/lib/nhost.ts
import { NhostClient } from '@nhost/nhost-js'
export const nhost = new NhostClient({
subdomain: '<your-subdomain>',
region: '<your-region>'
})
```
## Using custom URLs
There are cases where you might want to use a custom URL for one or more of the
services (e.g: when you are self-hosting or you are running services on custom
ports). You can do this by passing in the custom URLs when initializing the
Nhost client:
```ts title=src/lib/nhost.ts
import { NhostClient } from '@nhost/nhost-js'
export const nhost = new NhostClient({
authUrl: 'https://auth.mydomain.com/v1',
storageUrl: 'https://storage.mydomain.com/v1',
graphqlUrl: 'https://graphql.mydomain.com/v1',
functionsUrl: 'https://functions.mydomain.com/v1'
})
```
## GraphQL Support
The Nhost client has a small GraphQL client built-in which is great to use server-side or in very simple frontend apps. For more serious frontend apps, we recommend using a more complete GraphQL client such as:
- [Apollo Client](https://www.apollographql.com/docs/react/)
- [URQL](https://formidable.com/open-source/urql/)
- [React Query](https://react-query.tanstack.com/graphql)
- [SWR](https://swr.vercel.app/docs/data-fetching#graphql)
## Tokens and Permissions
The Nhost client manages refresh tokens and access tokens automatically. The correct authorization headers are set if the user is signed in. Both access tokens and refresh tokens are automatically refreshed in the background.
If the user is not signed in no authorization header is set. If no authorization header is set, requests to GraphQL and Storage resolve permissions using the `public` role.
## TypeScript Support
The Nhost JavaScript client is written in TypeScript and has full TypeScript support.

View File

@@ -1,4 +0,0 @@
{
"label": "Next.js",
"link": { "type": "generated-index", "slug": "/reference/nextjs" }
}

View File

@@ -1,115 +0,0 @@
---
title: 'Architecture'
sidebar_position: 3
---
When rendering a page from the server-side, Next.js needs to get some information from the client to determine their authentication status. Such communication is only available from cookies, and the Nhost client is designed to enable such a mechanism.
The cookie is `same-site` and is used only to store the refresh cookie and the expiration date of the JWT so the browser can proactively ask for a new JWT before it expires.
## Signing in
There is no need to activate SSR on the sign-in page as there is no session information to capture. As a result, it is recommended to code it as a classic static page.
The initial authentication would then work like this diagram:
```mermaid
sequenceDiagram
participant browser as Browser
participant next as Next.js
participant auth as Hasura Auth
browser->>auth: Authenticate
activate browser
activate auth
auth-->>browser: New refresh token + JWT + user data
deactivate auth
browser->>browser: Set the new refresh token in the cookie
deactivate browser
```
## Refreshing the JWT token
For security considerations, the Nhost clients never persist the JWT anywhere in the browser either in `localStorage` or in a cookie, and the JWT expires regularly (15 minutes by default). As a result, the Nhost client regularly calls the `hasura-auth` service to get a new JWT from a valid refresh token. The new JWT is emitted together with a new refresh token, while the old one is invalidated.
As a result, it is important to ensure both the browser and the Next.js will always use the last refresh token, as it will be the only one that is still valid.
The above diagram illustrates how a JWT is renewed, while making sure the refresh token is updated:
```mermaid
sequenceDiagram
participant browser as Browser
participant next as Next.js
participant auth as Hasura Auth
browser->>auth: Initial refresh token
activate auth
activate browser
auth-->>browser: New refresh token + JWT + user data
deactivate auth
browser->>browser: Set the new refresh token in the cookie
deactivate browser
```
## Loading a SSR page
When Nhost is activated on a specific page, the Next.js server will first check the refresh token in the cookie. If it is present, it will call the `hasura-auth` service to refresh the JWT, user data and the new refresh cookie.
It then:
- sets the new refresh cookie in the response headers
- hydrates the page with user data and JWT
- renders the page on the server side. At this stage, an authenticated GraphQL call can be done on the server-side as a valid JWT is available
Once the SSR page is loaded, it is still possible to refetch data from Hasura as the JWT will then be available in the browser, and kept up to date as explained in the above diagram.
```mermaid
sequenceDiagram
participant browser as Browser
participant next as Next.js
participant auth as Hasura Auth
participant hasura as Hasura
browser->>next: Request SSR page (refresh token in cookie)
activate browser
activate next
next->>auth: Refresh token
activate auth
auth-->>next: New refresh token + JWT + user data
deactivate auth
next->>next: Set the new refresh token in the cookie
next->>next: getServerSideProps - Hydrate JWT + user data
next->>hasura: GraphQL operation with JWT
activate hasura
hasura-->>next: GraphQL data
deactivate hasura
next->>next: Render page
next-->>browser: HTML with the new refresh token in the cookie, and JWT + user data
deactivate next
deactivate browser
browser->>hasura: Refetch data with JWT
activate browser
activate hasura
hasura-->>browser: Updated data
deactivate hasura
browser->>browser: Re-render data
deactivate browser
```
## Loading a static page
It is still possible to use the Nhost SDK in a more classic 'React' mode. It will then be handled in a very similar way, except that the refresh token will be stored in the cookie instead of `localStorage`.
```mermaid
sequenceDiagram
participant browser as Browser
participant next as Next.js
participant auth as Hasura Auth
participant hasura as Hasura
browser->>next: Request static page (refresh token in the cookie)
activate browser
activate next
next-->>browser: Static page (same cookie)
deactivate next
browser->>browser: Render "loading data" page
browser->>hasura: GraphQL operation with JWT
activate hasura
hasura-->>browser: GraphQL data
deactivate hasura
browser->>browser: Render data
deactivate browser
```

View File

@@ -1,189 +0,0 @@
---
title: 'Next.js'
sidebar_position: 1
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
The Nhost Next.js client exports a React provider for Next.js and React hooks that make it easier to work with Nhost in your Next.js app. The Next.js client is built on top of the Nhost React client and exports the same hooks and helpers.
## Installation
Install the Nhost Next.js client, React client together with GraphQL:
<Tabs groupId="package-manager">
<TabItem value="npm" label="npm" default>
```bash
npm install @nhost/nextjs graphql
```
</TabItem>
<TabItem value="yarn" label="Yarn">
```bash
yarn add @nhost/nextjs graphql
```
</TabItem>
</Tabs>
## Initializing
Initialize a single `nhost` instance using your Nhost `subdomain` and `region`,
and wrap your app with the `NhostProvider`:
```tsx title=pages/_app.tsx
import { NhostClient, NhostProvider } from '@nhost/nextjs'
import type { AppProps } from 'next/app'
const nhost = new NhostClient({
subdomain: '<your-subdomain>',
region: '<your-region>'
})
function MyApp({ Component, pageProps }: AppProps) {
return (
<NhostProvider nhost={nhost} initial={pageProps.nhostSession}>
<Component {...pageProps} />
</NhostProvider>
)
}
export default MyApp
```
:::info
The `nhost` instance created with the `NhostClient` above is the same as the [JavaScript Nhost client](/reference/javascript).
:::
## Using custom URLs
There are cases where you might want to use a custom URL for one or more of the
services (e.g: when you are self-hosting or you are running services on custom
ports). You can do this by passing in the custom URLs when initializing the
Nhost client:
```tsx title=pages/_app.tsx
import { NhostClient, NhostProvider } from '@nhost/nextjs'
import type { AppProps } from 'next/app'
const nhost = new NhostClient({
authUrl: 'https://auth.mydomain.com/v1',
storageUrl: 'https://storage.mydomain.com/v1',
graphqlUrl: 'https://graphql.mydomain.com/v1',
functionsUrl: 'https://functions.mydomain.com/v1'
})
function MyApp({ Component, pageProps }: AppProps) {
return (
<NhostProvider nhost={nhost} initial={pageProps.nhostSession}>
<Component {...pageProps} />
</NhostProvider>
)
}
export default MyApp
```
## Server-Side Rendering (SSR)
You need to load the session from the server first from `getServerSideProps`. Once it is done, the `_app` component will make sure to load or update the session through `pageProps`.
```jsx title=pages/ssr-page.tsx
import { NextPageContext } from 'next'
import React from 'react'
import {
getNhostSession,
NhostSession,
useAccessToken,
useAuthenticated,
useUserData
} from '@nhost/nextjs'
export async function getServerSideProps(context: NextPageContext) {
const nhostSession = await getNhostSession('<Your Nhost Backend URL>', context)
return {
props: {
nhostSession
}
}
}
const ServerSidePage: React.FC<{ initial: NhostSession }> = () => {
const isAuthenticated = useAuthenticated()
const user = useUserData()
const accessToken = useAccessToken()
if (!isAuthenticated) {
return <div>User it not authenticated</div>
}
return (
<div>
<h1>{user?.displayName} is authenticated</h1>
<div>Access token: {accessToken}</div>
</div>
)
}
export default ServerSidePage
```
## Apollo GraphQL
You can use Apollo's GraphQL Client together with Next.js and Nhost.
### Installation
<Tabs groupId="package-manager">
<TabItem value="npm" label="npm" default>
```bash
npm install @nhost/react-apollo @apollo/client
```
</TabItem>
<TabItem value="yarn" label="Yarn">
```bash
yarn add @nhost/react-apollo @apollo/client
```
</TabItem>
</Tabs>
### Initializing
Wrap the Next.js app with the `NhostApolloProvider` and make sure the `NhostApolloProvider` is nested inside the `NhostProvider`.
```jsx title=pages/_app.js
import type { AppProps } from 'next/app'
import { NhostClient, NhostProvider } from '@nhost/nextjs'
import { NhostApolloProvider } from '@nhost/react-apollo'
const nhost = new NhostClient({
subdomain: '<your-subdomain>',
region: '<your-region>'
})
function MyApp({ Component, pageProps }: AppProps) {
return (
<NhostProvider nhost={nhost} initial={pageProps.nhostSession}>
<NhostApolloProvider nhost={nhost}>
<Component {...pageProps} />
</NhostApolloProvider>
</NhostProvider>
)
}
export default MyApp
```
Since Next.js uses React you can read more about how to use Apollo and GraphQL in [here](/reference/react/apollo#usage).

View File

@@ -1,43 +0,0 @@
---
title: 'Protecting routes'
sidebar_position: 3
---
Create an `auth-protected.js` file:
You can protect routes using a `authProtected` component. This component can be used
```tsx title=components/authProtected.js
import { useRouter } from 'next/router'
import { useAuthenticationStatus } from '@nhost/nextjs'
export function authProtected(Comp) {
return function AuthProtected(props) {
const router = useRouter()
const { isLoading, isAuthenticated } = useAuthenticationStatus()
if (isLoading) {
return <div>Loading...</div>
}
if (!isAuthenticated) {
router.push('/login')
return null
}
return <Comp {...props} />
}
}
```
Then wrap the Next.js page with `authProtected` to protect the page so only signed-in users can access the page.
```jsx title=pages/index.js
import { authProtected } from 'components/authProtected'
function Index() {
return <div>Only signed-in users can see this page.</div>
}
export default authProtected(Index)
```

View File

@@ -1,4 +0,0 @@
{
"label": "React",
"link": { "type": "generated-index", "slug": "/reference/react" }
}

View File

@@ -1,113 +0,0 @@
---
title: Apollo GraphQL
sidebar_position: 3
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
## Installation
<Tabs groupId="package-manager">
<TabItem value="npm" label="npm" default>
```bash
npm install @nhost/react @nhost/react-apollo @apollo/client graphql
```
</TabItem>
<TabItem value="yarn" label="Yarn">
```bash
yarn add @nhost/react @nhost/react-apollo @apollo/client graphql
```
</TabItem>
</Tabs>
## Initializing
Wrap the React app with the `NhostApolloProvider` and make sure the `NhostApolloProvider` is nested inside the `NhostProvider`.
This way, the correct authentication headers are set automatically for GraphQL requests being made by Apollo.
```jsx
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import { NhostApolloProvider } from '@nhost/react-apollo'
import { NhostClient, NhostProvider } from '@nhost/react'
const nhost = new NhostClient({
subdomain: '<app-subdomain>',
region: '<app-region>'
})
ReactDOM.render(
<React.StrictMode>
<NhostProvider nhost={nhost}>
<NhostApolloProvider nhost={nhost}>
<App />
</NhostApolloProvider>
</NhostProvider>
</React.StrictMode>,
document.getElementById('root')
)
```
## Usage
You can now use all [Apollo hooks](https://www.apollographql.com/docs/react/api/react/hooks/) (queries, mutations, subscriptions) and the correct authentication headers will automatically be set based on the authentication status of the user.
### Example
```jsx
import { gql, useQuery } from '@apollo/client'
import { useAuthenticated } from '@nhost/react'
const GET_BOOKS = gql`
query Books {
books {
id
title
}
}
`
export const BooksQuery = () => {
const isAuthenticated = useAuthenticated()
const { loading, data, error } = useQuery(GET_BOOKS)
if (loading) {
return <div>Loading...</div>
}
if (!isAuthenticated) {
return <div>You must be authenticated to see this page</div>
}
if (error) {
return <div>Error in the query {error.message}</div>
}
return (
<div>
<ul>
{data?.books.map((book) => (
<li key={book.id}>{book.name}</li>
))}
</ul>
</div>
)
}
```
## Hooks
### `useAuthQuery`
The `useAuthQuery` hook works just like Apollo's [`useQuery`](https://www.apollographql.com/docs/react/api/react/hooks/#usequery) except the query will be skipped if the user is not authenticated.
## Read more
Learn more about the [GraphQL Apollo Client](https://www.apollographql.com/docs/react/).

View File

@@ -1,89 +0,0 @@
---
title: 'React'
sidebar_position: 1
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
The Nhost React client exports a React provider, hooks, and helpers that make it easier to work with Nhost in your React app. If you're using React with Next.js, you should use the [Nhost Next.js client](/reference/nextjs).
## Installation
Install the Nhost React client together with GraphQL:
<Tabs groupId="package-manager">
<TabItem value="npm" label="npm" default>
```bash
npm install @nhost/react graphql
```
</TabItem>
<TabItem value="yarn" label="Yarn">
```bash
yarn add @nhost/react graphql
```
</TabItem>
</Tabs>
## Initializing
Initialize a single `nhost` instance using your Nhost `subdomain` and `region`:
```jsx title=src/lib/nhost.js
import { NhostClient } from '@nhost/react'
export const nhost = new NhostClient({
subdomain: '<your-subdomain>',
region: '<your-region>'
})
```
## Using custom URLs
There are cases where you might want to use a custom URL for one or more of the
services (e.g: when you are self-hosting or you are running services on custom
ports). You can do this by passing in the custom URLs when initializing the
Nhost client:
```ts title=src/lib/nhost.ts
import { NhostClient } from '@nhost/react'
export const nhost = new NhostClient({
authUrl: 'https://auth.mydomain.com/v1',
storageUrl: 'https://storage.mydomain.com/v1',
graphqlUrl: 'https://graphql.mydomain.com/v1',
functionsUrl: 'https://functions.mydomain.com/v1'
})
```
Import `nhost` and wrap your app with the `NhostProvider`.
```jsx title=src/App.tsx
import React from 'react'
import ReactDOM from 'react-dom'
import { NhostClient, NhostProvider } from '@nhost/react'
import { nhost } from './lib/nhost'
import App from './App'
ReactDOM.render(
<React.StrictMode>
<NhostProvider nhost={nhost}>
<App />
</NhostProvider>
</React.StrictMode>,
document.getElementById('root')
)
export { nhost }
```
:::info
The `nhost` instance created with the `NhostClient` above is the same as the [JavaScript Nhost client](/reference/javascript).
:::

View File

@@ -1,82 +0,0 @@
---
title: Protecting routes
sidebar_position: 2
---
## React Router
You can protect routes by creating a wrapper component (`ProtectedRoute`) and use that component to make sure the user is authenticated (signed-in) before seeing the component that is protected.
This means you can have certain parts of your app only available for signed-in users.
:::info
This example uses [React Router v6](https://reactrouter.com) for routing.
:::
```jsx title=src/components/ProtectedRoute.js
import { useAuthenticationStatus } from '@nhost/react'
import { Navigate, Outlet, useLocation } from 'react-router-dom'
export function ProtectedRoute() {
const { isAuthenticated, isLoading } = useAuthenticationStatus()
const location = useLocation()
if (isLoading) {
return <div>Loading...</div>
}
if (!isAuthenticated) {
return <Navigate to="/sign-in" state={{ from: location }} replace />
}
return <Outlet />
}
```
The `ProtectedRoute` component uses the [`useAuthenticationStatus`](/reference/react/use-authentication-status) hook to get the authentication status of the user.
If the user is **not** authenticated (not signed-in), the user is redirected to `/sign-in`. Otherwise, the child route element is rendered for the signed-in user.
Finally, we use a [layout route](https://reactrouter.com/docs/en/v6/getting-started/concepts#layout-routes) in `App.js` to wrap the `ProtectedRoute` component around the routes we want to protect:
```jsx title=src/App.js
import { NhostProvider } from '@nhost/react'
import { BrowserRouter, Route, Routes } from 'react-router-dom'
import { ProtectedRoute } from './components/ProtectedRoute'
import { nhost } from './lib/nhost'
import Dashboard from './pages/Dashboard'
import Home from './pages/Home'
import Login from './pages/Login'
import Profile from './pages/Profile'
export function App() {
return (
<NhostProvider nhost={nhost}>
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/login" element={<Login />} />
<Route path="/dashboard" element={<ProtectedRoute />}>
<Route index element={<Dashboard />} />
<Route path="profile" element={<Profile />} />
</Route>
</Routes>
</BrowserRouter>
</NhostProvider>
)
}
```
Where `nhost` comes from `lib/nhost.js` like this:
```jsx title="src/lib/nhost.js"
import { NhostClient } from '@nhost/react'
const nhost = new NhostClient({
subdomain: '<your-subdomain>',
region: '<your-region>'
})
export { nhost }
```

View File

@@ -1,4 +0,0 @@
{
"label": "Vue",
"link": { "type": "generated-index", "slug": "/reference/vue" }
}

View File

@@ -1,80 +0,0 @@
---
title: Apollo GraphQL
sidebar_position: 3
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
A ready-to-use Apollo client is available in the `@nhost/apollo` package. It is compatible with the Nhost Vue client and [Vue Apollo v4](https://v4.apollo.vuejs.org/).
## Installation
<Tabs groupId="package-manager">
<TabItem value="npm" label="npm" default>
```bash
npm install @nhost/vue @nhost/apollo @apollo/client graphql @vue/apollo-composable
```
</TabItem>
<TabItem value="yarn" label="Yarn">
```bash
yarn add @nhost/vue @nhost/apollo @apollo/client graphql @vue/apollo-composable
```
</TabItem>
</Tabs>
## Initializing
Create the Nhost Apollo client in your `main` file, then provide it to your app using the `DefaultApolloClient`:
```js title=src/main.js
import { createApp } from 'vue'
import { createApolloClient } from '@nhost/apollo'
import { NhostClient } from '@nhost/vue'
import { DefaultApolloClient } from '@vue/apollo-composable'
import App from './App.vue'
const nhost = new NhostClient({
subdomain: '<your-subdomain>',
region: '<your-region>'
})
const apolloClient = createApolloClient({ nhost })
createApp(App).provide(DefaultApolloClient, apolloClient).use(nhost).mount('#app')
```
## Usage
You can now use all [Apollo Vue composables](https://v4.apollo.vuejs.org/guide-composable/) (queries, mutations, subscriptions) and the correct authentication headers will automatically be set based on the authentication status of the user.
### Example
```html
<script setup>
import { useQuery } from '@vue/apollo-composable'
import { gql } from '@apollo/client/core'
const { loading, result, error } = useQuery(gql`
query Books {
books {
id
title
}
}
`)
</script>
<template>
<div v-if="loading">Loading query...</div>
<div v-else>
<ul>
<li v-for="book in result.books">{{ book.name }}</li>
</ul>
</div>
</template>
```

View File

@@ -1,70 +0,0 @@
---
title: 'Vue'
sidebar_position: 1
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
The Nhost Vue client exports a Nhost client that can be installed as a [Vue plugin](https://vuejs.org/guide/reusability/plugins.html), and [composables](https://vuejs.org/guide/reusability/composables.html) that make it easier to work with Nhost in your Vue app.
## Installation
Install the Nhost Vue client together with GraphQL:
<Tabs groupId="package-manager">
<TabItem value="npm" label="npm" default>
```bash
npm install @nhost/vue graphql
```
</TabItem>
<TabItem value="yarn" label="Yarn">
```bash
yarn add @nhost/vue graphql
```
</TabItem>
</Tabs>
## Initializing
Initialize a single `nhost` instance using your Nhost `subdomain` and `region`,
and install it as a plugin in your Vue app:
```js title=src/main.js
import { NhostClient } from '@nhost/vue'
import { createApp } from 'vue'
import App from './App.vue'
const nhost = new NhostClient({
subdomain: '<your-subdomain>',
region: '<your-region>'
})
createApp(App).use(nhost).mount('#app')
```
## Using custom URLs
There are cases where you might want to use a custom URL for one or more of the
services (e.g: when you are self-hosting or you are running services on custom
ports). You can do this by passing in the custom URLs when initializing the
Nhost client:
```js title=src/main.js
import { NhostClient } from '@nhost/vue'
import { createApp } from 'vue'
import App from './App.vue'
const nhost = new NhostClient({
authUrl: 'https://auth.mydomain.com/v1',
storageUrl: 'https://storage.mydomain.com/v1',
graphqlUrl: 'https://graphql.mydomain.com/v1',
functionsUrl: 'https://functions.mydomain.com/v1'
})
createApp(App).use(nhost).mount('#app')
```

View File

@@ -1,46 +0,0 @@
---
title: Protecting routes
sidebar_position: 2
---
## Vue Router
You can protect routes by creating a navigation guard that will inquire for the authentication status of the user.
This means you can have certain parts of your app only available for signed-in users.
:::info
This example uses [Vue Router v4](https://router.vuejs.org/) for routing.
:::
```js title=src/main.js
import { createApp } from 'vue'
import { createRouter, createWebHashHistory } from 'vue-router'
import { NhostClient } from '@nhost/vue'
import App from './App.vue'
const nhost = new NhostClient({
subdomain: '<your-subdomain>',
region: '<your-region>'
})
const router = createRouter({
history: createWebHashHistory(),
routes: [
// your custom routes
]
})
router.beforeEach(async (to) => {
const authenticated = await nhost.auth.isAuthenticatedAsync()
if (!authenticated) {
// The `/profile` route is protected and will redirect to `/signin` if not authenticated
if (to.path === '/profile') {
return '/signin'
}
}
return true
})
createApp(App).use(router).use(nhost).mount('#app')
```

View File

@@ -1,4 +0,0 @@
{
"label": "Run",
"position": 9
}

View File

@@ -1,48 +0,0 @@
---
title: Overview
sidebar_label: Overview
sidebar_position: 1
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
Nhost Run enables you to seamlessly incorporate your custom software within your project environment. With this feature, you can run various applications, including homegrown backend services, agents, extensions for your GraphQL API through remote schemas or actions, data-processing workloads, etc., all in close proximity to your database for low latency, speed, and efficiency.
![Nhost Architecture Diagram](/img/run/overview.png)
:::info
Currently Nhost Run is in public beta. If you find any bugs or if you have any feedback and suggestions, please reach out to us via [email](mailto:support@nhost.io), [GitHub](https://github.com/nhost/nhost/issues), or [Discord](https://discord.com/invite/9V7Qb2U)
:::
## Use Cases
Nhost Run allows you to expand and truly customize your backend in multiple ways:
1. Homegrown Backend Services: Deploy and execute your custom backend services within your project environment.
2. GraphQL API Extensions: Extend your GraphQL API functionalities by incorporating remote schemas or actions.
3. Data-Processing Workloads: Execute data-processing tasks in close proximity to your database for enhanced efficiency.
4. OSS and third-party software: Redis, memcache, datadog agents, mysql, mongodb... anything your application needs.
## Advantages
Nhost Run offers several key advantages for running workloads alongside your project:
1. **Minimal Latency**: By running workloads alongside your project environment, Nhost Run reduces latency between services. This means that the communication and data exchange between different components of your project can occur quickly and efficiently.
2. **Improved Reliability**: Nhost Run eliminates the dependency on external internet connectivity. This increased reliability ensures that your workloads continue to function even in scenarios where internet access may be limited or disrupted.
3. **No Egress Costs**: With Nhost Run, you won't incur additional egress costs for transferring data between your project and the cloud infrastructure. This cost-saving benefit allows you to manage your expenses more effectively.
4. **Integrated operations**: Develop, build, manage and scale your own workloads the same way you can manage your Nhost Project.
By leveraging Nhost Run, you can optimize the performance, reliability, and cost-efficiency of your project by running workloads alongside it.
## Requirements
Nhost Run works with container images built for the **arm architecture**. Images can be pulled from the [Nhost's private registry](/run/registry) or from any other publicly available registry.
## Roadmap
Some missing functionality we are currently working on and should be added soon:
1. Run services with the CLI alongside your project
2. Ability to connect services to repositories for automated building and deployment (currently this needs to be done via a third party CI, see [Deployment via CI](/run/ci) for more details).
3. Expose TCP/UDP ports

View File

@@ -1,105 +0,0 @@
---
title: 'Serverless Functions'
sidebar_position: 8
image: /img/og/serverless-functions.png
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
With Nhost, you can deploy Serverless Functions to execute custom code. Each Serverless Function is its HTTP endpoint.
Serverless functions can be used to handle [event triggers](/database/event-triggers), form submissions, integrations (e.g. Stripe, Slack, etc), and more.
## Create a Serverless Function
Every `.ts` (TypeScript) and `.js` (JavaScript) file in the `functions/` folder of your Nhost project is its own Serverless Function.
<Tabs groupId="language">
<TabItem value="ts" label="TypeScript" default>
```ts title=functions/test.ts
import { Request, Response } from 'express'
export default (req: Request, res: Response) => {
res.status(200).send(`Hello ${req.query.name}!`)
}
```
:::info
To get the `Request`, and `Response` types you can install the `@types/express` package.
```bash
npm install -D @types/express
# or yarn
yarn add -D @types/express
# or pnpm
pnpm add -D @types/express
```
:::
</TabItem>
<TabItem value="js" label="JavaScript">
```js title=functions/test.js
export default (req, res) => {
res.status(200).send(`Hello ${req.query.name}!`)
}
```
</TabItem>
</Tabs>
## Deploying Serverless Functions
Serverless Functions are automatically deployed using Nhost's [Git integration](/platform/git).
All Serverless Functions are deployed with the following options:
- Node v16
- 1024 MB memory (can be upgraded)
- 10 seconds timeout (can be upgraded)
- 6 MB request and response payload size limit
## Routing
HTTP endpoints are automatically generated based on the file structure inside `functions/`.
Here's an example of four Serverless Functions with their files and their HTTP endpoints:
| File | HTTP Endpoint |
| --------------------------- | ------------------------------------------------------------------ |
| `functions/index.js` | `https://[subdomain].functions.[region].nhost.run/v1/` |
| `functions/users/index.ts` | `https://[subdomain].functions.[region].nhost.run/v1/users` |
| `functions/users/active.ts` | `https://[subdomain].functions.[region].nhost.run/v1/users/active` |
| `functions/my-company.js` | `https://[subdomain].functions.[region].nhost.run/v1/my-company` |
You can prepend files and folders with an underscore (`_`) to prevent them from being treated as Serverless Functions and
be turned into HTTP endpoints. This is useful if you have, for example, a utils file (`functions/_utils.js`) or a utils-f
older (`functions/_utils/<utils-files>.js`).
## Environment Variables
[Environment variables](/platform/environment-variables) are available inside your Serverless Functions. Both in production and when running Nhost locally using the [Nhost CLI](/cli).
The same [environment variables that are used to configure event triggers](https://docs.nhost.io/database/event-triggers#format) can be used to authenticate regular serverless functions.
## Examples
We have multiple examples of Serverless Functions in our [Nhost repository](https://github.com/nhost/nhost/tree/main/examples/serverless-functions/functions).
## Billing
Serverless Functions are billed per GB-sec or GB-hour. 1 GB-hour is 3600 GB-seconds.
1 GB-sec is 1 Serverless Function with 1 GB of RAM running for 1 second. If 1 Serverless Function with 1 GB of RAM runs for 3600 seconds, that's the equivalent of 1 GB-hour.
## Regions
Serverless Functions are always deployed to the same region as your project.
## Local Debugging
Use `nhost logs functions -f` to see the logs of your Serverless Functions when develop locally with the [Nhost CLI](/cli).

View File

@@ -1,4 +0,0 @@
{
"label": "Storage",
"position": 7
}

View File

@@ -1,57 +0,0 @@
---
title: "Example: CRM System"
sidebar_label: "Example: CRM System"
sidebar_position: 3
---
Let's say you want to build a CRM system and you want to store files for customers. This is one way how you could do that.
Start with, you would have two tables:
1. `customers` - Customer data.
2. `customer_files` - What file belongs to what customer
```text
- customers
- id
- name
- address
customer_files
- id
- customer_id (Foreign Key to `customers.id`)
- file_id (Foreign Key to `storage.files.id`)
```
You would also create a [Hasura Relationship](https://hasura.io/docs/latest/graphql/core/databases/postgres/schema/table-relationships/index/) (GraphQL relationship) between between `customers` and `customer_files` and between `customer_files` and `storage.files`.
With the two tables and GraphQL relationships in place, you can query customers and the customer's files like this:
```graphql
query {
customers {
# customers table
id
name
customer_files {
# customer_files table
id
file {
# storage.files table
id
name
size
mimeType
}
}
}
}
```
The file upload process would be as follows:
1. Upload a file.
2. Get the returned file id.
3. Insert (GraphQL Mutation) the file `id` and the customer's `id` into the `customer_files` table.
This would allow you to upload and download files belonging to specific customers in your CRM system.

View File

@@ -1,214 +0,0 @@
// @ts-check
// Note: type annotations allow type checking and IDEs autocompletion
const lightCodeTheme = require('prism-react-renderer/themes/github')
const darkCodeTheme = require('prism-react-renderer/themes/dracula')
const getBaseUrl = () => {
if (process.env.VERCEL_ENV === 'production') {
return 'https://docs.nhost.io'
} else if (process.env.VERCEL_ENV === 'preview') {
return `https://${process.env.VERCEL_URL}`
} else {
return `http://localhost:3000`
}
}
/** @type {import('@docusaurus/types').Config} */
const config = {
title: 'Nhost Docs',
tagline: 'Nhost is an open source Firebase alternative with GraphQL.',
url: getBaseUrl(),
trailingSlash: false,
baseUrl: '/',
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'warn',
favicon: 'img/favicon.png',
organizationName: 'nhost',
projectName: 'docs',
markdown: {
mermaid: true
},
themes: ['@docusaurus/theme-mermaid'],
scripts: [
{ src: 'https://plausible.io/js/script.js', defer: true, 'data-domain': 'docs.nhost.io' }
],
plugins: [require.resolve('docusaurus-plugin-image-zoom')],
presets: [
[
'classic',
/** @type {import('@docusaurus/preset-classic').Options} */
({
sitemap: {
changefreq: 'weekly',
priority: 0.5
},
docs: {
path: 'docs',
routeBasePath: '/',
breadcrumbs: false,
sidebarPath: require.resolve('./sidebars.js'),
editUrl: 'https://github.com/nhost/nhost/edit/main/docs/'
},
theme: {
customCss: require.resolve('./src/css/custom.css')
}
})
]
],
themeConfig:
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
({
image: 'img/splash.png',
colorMode: {
defaultMode: 'light',
disableSwitch: false,
respectPrefersColorScheme: true
},
metadata: [{ name: 'robots', content: 'max-image-preview:large' }],
navbar: {
hideOnScroll: true,
logo: {
alt: 'Nhost',
src: 'img/logo.svg',
srcDark: 'img/logo-dark.svg',
href: 'https://nhost.io'
},
items: [
{
type: 'doc',
docId: 'index',
position: 'left',
label: 'Documentation'
},
{
type: 'doc',
docId: 'reference/index',
position: 'left',
label: 'Reference'
},
{
href: 'https://github.com/nhost/nhost',
className: 'header-github-link',
position: 'right',
'aria-label': 'Github repository'
},
{
href: 'https://app.nhost.io',
className: 'header-get-started-link',
position: 'right',
label: 'Dashboard'
}
]
},
footer: {
style: 'dark',
links: [
{
title: 'Product',
items: [
{
label: 'Product',
href: 'https://nhost.io/#product'
},
{
label: 'Features',
href: 'https://nhost.io/#features'
},
{
label: 'Pricing',
href: 'https://nhost.io/pricing'
}
]
},
{
title: 'Docs',
items: [
{
label: 'Documentation',
to: '/'
},
{
label: 'Reference',
to: '/reference'
}
]
},
{
title: 'Community',
items: [
{
label: 'GitHub',
href: 'https://github.com/nhost/nhost'
},
{
label: 'Twitter',
href: 'https://twitter.com/nhostio'
},
{
label: 'LinkedIn',
href: 'https://www.linkedin.com/company/nhost/'
},
{
label: 'Discord',
href: 'https://discord.com/invite/9V7Qb2U'
}
]
},
{
title: 'More',
items: [
{
label: 'Blog',
href: 'https://nhost.io/blog'
},
{
label: 'Privacy Policy',
href: 'https://nhost.io/privacy-policy'
},
{
label: 'Terms of Service',
href: 'https://nhost.io/terms-of-service'
}
]
}
],
copyright: `Copyright © ${new Date().getFullYear()} <a href="https://nhost.io" target="_blank" rel="noopener noreferrer">Nhost</a>. All rights reserved.`
},
prism: {
theme: lightCodeTheme,
darkTheme: darkCodeTheme,
defaultLanguage: 'javascript',
additionalLanguages: ['cue', 'toml'],
magicComments: [
{
className: 'code-block-error-line',
line: 'code-block-error-line'
},
{
className: 'code-block-success-line',
line: 'code-block-success-line'
}
]
},
algolia: {
appId: '3A3MJQTKHU',
apiKey: 'a76361eaed8ebcd4cf5d9ae2f0c9e746',
indexName: 'nhost',
contextualSearch: true
},
zoom: {
selector: '.markdown :not(em) > img',
config: {
// options you can specify via https://github.com/francoischalifour/medium-zoom#usage
background: {
light: 'rgb(255, 255, 255)',
dark: 'rgb(50, 50, 50)'
}
}
}
})
}
module.exports = config

11
docs/favicon.svg Normal file
View File

@@ -0,0 +1,11 @@
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="100" height="100" fill="white"/>
<g clip-path="url(#clip0_6_23)">
<path d="M72.7731 30.8249L51.9601 18.8068C50.0928 17.7311 47.7748 17.7311 45.9037 18.8068C44.0364 19.8862 42.8774 21.8937 42.8774 24.0488V25.6169L41.5214 24.8329C39.6541 23.7572 37.3361 23.7572 35.465 24.8329C33.5977 25.9123 32.4387 27.9198 32.4387 30.0787V31.6468L31.0827 30.8628C29.2154 29.7871 26.8974 29.7871 25.0263 30.8628C23.159 31.9422 22 33.9497 22 36.1086V73.7652C22 74.8484 22.6287 75.8559 23.6059 76.3256C24.5794 76.799 25.7611 76.6703 26.6095 75.9999L36.9308 67.8603L52.8464 77.049C53.2858 77.3028 53.7782 77.4278 54.2705 77.4278C54.7629 77.4278 55.2553 77.299 55.6947 77.049C56.5734 76.5415 57.1188 75.5984 57.1188 74.5833V51.9219C57.1188 48.2025 55.119 44.7406 51.8995 42.8809L46.6801 39.8659V24.0526C46.6801 23.2496 47.1119 22.4997 47.8089 22.0982C48.5058 21.6967 49.3694 21.6967 50.0663 22.0982L70.8793 34.1125C72.9284 35.2943 74.201 37.5025 74.201 39.8659V68.0837C74.201 68.8867 73.7692 69.6367 73.0723 70.0381L67.5575 73.2235V45.892C67.5575 42.1726 65.5577 38.7107 62.3382 36.851L49.5247 29.4538V33.836L60.4406 40.1387C62.4897 41.3204 63.7623 43.5248 63.7623 45.892V74.8636C63.7623 75.8749 64.3077 76.8218 65.1865 77.3293C65.6258 77.5831 66.1182 77.7081 66.6106 77.7081C67.103 77.7081 67.5954 77.5793 68.0348 77.3293L74.9737 73.322C76.841 72.2425 78 70.2351 78 68.0762V39.8584C77.9924 36.1503 75.9926 32.6846 72.7731 30.8249ZM49.9943 46.1685C52.0434 47.3503 53.3161 49.5547 53.3161 51.9219V72.9432L40.1351 65.3339L44.3659 62.0008C45.8317 60.8456 46.6726 59.1146 46.6726 57.2473V44.2558L49.9943 46.1685ZM42.8774 42.059V57.2398C42.8774 57.9367 42.563 58.5844 42.0176 59.0124L25.7952 71.8032V36.1048C25.7952 35.3019 26.227 34.5519 26.9239 34.1504C27.6208 33.7489 28.4844 33.7489 29.1813 34.1504L32.4387 36.0291V62.9401L36.2339 59.9479V30.0787C36.2339 29.2758 36.6657 28.5258 37.3626 28.1243C38.0595 27.7228 38.9231 27.7228 39.62 28.1243L42.8774 30.003V37.6729L39.0822 35.4799V39.8659L42.8774 42.059Z" fill="#0052CD"/>
</g>
<defs>
<clipPath id="clip0_6_23">
<rect width="56" height="59.7081" fill="white" transform="translate(22 18)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -1,22 +0,0 @@
#!/bin/bash
CWD=$(PWD)
cd ../examples/docker-compose
cp .env.example .env
export AUTH_CLIENT_URL="https://my-app.com"
docker-compose pull auth storage
docker-compose up -d
echo -n "Waiting for hasura-auth to be ready"
while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' http://localhost:1337/v1/auth/healthz)" != "200" ]];
do
echo -n "."
sleep 1;
done
echo
cd $CWD
curl http://localhost:1337/v1/auth/openapi.json | jq '.' > static/openapi/hasura-auth.json
cd ../examples/docker-compose
docker-compose down

View File

@@ -0,0 +1,394 @@
---
title: "Assistants"
description: "Deploy your customized AI assistants"
icon: wand-magic-sparkles
---
AI Assistants leverages ChatGPT and allows you to define different AI assistants with pre-defined tools and accesses to your data. This pre-defined access is expressed as GraphQL queries or mutations and/or webhooks and are unique to each AI assistant, meaning you can create different AI assistants with access to different type of data. In addition, AI assistants are fully integrated with Nhost permissions which means only data the user has access to will be made available to each AI assistant session.
## Demo
To demonstrate how to configure and use AI Assistants we are going to be using a sample project with a database full of movies.
## Creating AI Assistants
The first step is to create an AI assistant. You can create an AI assistant by utilizing the `insertAssistant` mutation or by using the dashboard. For instance:
<Tabs>
<Tab title="dashboard">
![dashboard](/images/guides/ai/assistants/dashboard.png)
</Tab>
<Tab title="graphql">
```graphql
mutation {
graphite {
insertAssistant(object: {
name: "awesome-assistant-for-my-users",
description: "My Awesome assistant for my users",
instructions: "You are a useful assistant for this demo application. You are an expert on movies. You also know a bit about deep questions.",
model: "gpt-3.5-turbo-1106",
graphql: [
{
description: "get movies with higher score than the input"
name: "GetMoviesWithScoreHigherThan",
query: "query ($score: numeric!) { movies(where: {score: {_gt: $score}}) { name overview score }}",
arguments: [
{
name: "score",
description: "score to compare against",
type: "number",
required: true,
}
]
},
{
description: "search movies using natural language",
name: "SearchMovies",
query: "query GraphiteSearchMovies($query: String!) { graphiteSearchMovies(args: {query: $query, amount: 5}) { id name overview genre } }",
arguments: [
{
name: "query",
description: "Text to search",
type: "string",
required: true,
}
]
}
{
description: "insert a movie into the database",
name: "InsertMovie",
query: "mutation InsertMovie($name: String, $overview: String, $genre: String, $crew: String, $budget: bigint, $revenue: bigint, $country: String, $score: numeric) { insertMovie(object: {name: $name, overview: $overview, score: $score, genre: $genre, crew: $crew, budget: $budget, revenue: $revenue, country: $country}) {id}}",
arguments: [
{
name: "name",
description: "Name of the movie",
type: "string",
required: true,
},
{
name: "overview",
description: "Overview of the movie",
type: "string",
required: true,
},
{
name: "genre",
description: "Genre of the movie",
type: "string",
required: true,
},
{
name: "crew",
description: "Crew of the movie",
type: "string",
required: true,
},
{
name: "budget",
description: "Budget of the movie",
type: "number",
required: true,
},
{
name: "revenue",
description: "Revenue generated by the movie",
type: "number",
required: true,
},
{
name: "country",
description: "Country of origin for the movie",
type: "string",
required: true,
},
{
name: "score",
description: "Score of the movie",
type: "number",
required: true,
},
]
}
],
webhooks: [
{
name: "answer_deep_questions",
description: "Answers deep questions about life, the universe and everything",
URL: "https://local.functions.nhost.run/v1/meaning",
arguments: [
{
name: "question",
description: "question that needs answer",
type: "string",
required: true,
}
],
}
]
}) {
assistantID
}
}
}
```
</Tab>
</Tabs>
### Tools
In order to enhance our AI assistants we are leveraging tools. Tools can be either queries or mutations performed against your project's GraphQL engine or they can also be any arbitrary endpoint you choose. Using the example above, we added 4 different tools to our AI Assistant:
- The GraphQL query `GetMoviesWithScoreHigherThan`, which will allow the AI Assistant to query movies based on their score.
- The GraphQL query `SearchMovies`, which leverages [auto-embeddings](auto-embeddings) and will allow the AI Assistant to query movies using natural language.
- The GraphQL mutation `InsertMovie`, which will let the AI Assistant to insert new movies directly into the database.
- The webhook `answer_deep_questions`, which is a serverless function that can respond to deep questions about life and the universe.
With a good name and description, the AI Assistant will know that certain information can be made available or that certain actions are at its disposal via these tools. We will see more in detail how this works a bit later but for now suffices to say that:
- ChatGPT doesn't actually query the data or perform any action, it tells graphite to execute the tools on its behalf and provide the result.
- graphite will respect the user session when calling tools so permissions are respected.
- GraphQL queries and mutations are performed against the project's GraphQL endpoint
- For webhooks you can specify any URL you want and graphite will perform a POST with a JSON object as body
## Starting a session
To start a session you can use the mutation `startSession` and pass the id of the assistant you want to use. For instance, to start a session using the assistant we created in the previous section:
```graphql
mutation {
graphite {
startSession(assistantID:"asst_xolmq2yeBBRi8CKGc5YWXGaA") {
sessionID
}
}
}
```
You should receive a response similar to:
```json
{
"data": {
"graphite": {
"startSession": {
"sessionID": "thread_dxHHNpQryFpJ8wXXD9Q2fQJg"
}
}
}
}
```
## Interacting with the Assistant
Now that you have started a session you can send your first message:
```graphql
mutation {
graphite {
sendMessage(
sessionID:"thread_dxHHNpQryFpJ8wXXD9Q2fQJg",
message: "what is the meaning of life?"
prevMessageID: "",
) {
sessionID
messages {
id
createdAt
role
message
}
}
}
}
```
The field `prevMessageID` is used to only return messages after this one. As this is the beginning of the conversation we send an empty string to get all messages back. After executing that mutation we should receive a response like:
```json
{
"data": {
"graphite": {
"sendMessage": {
"sessionID": "thread_dxHHNpQryFpJ8wXXD9Q2fQJg",
"messages": [
{
"id": "msg_5LQa4sWH2QaN0xi5ezxHQUK1",
"createdAt": "2023-12-08T09:33:12Z",
"role": "user",
"message": ""
},
{
"id": "msg_Xv6XvZrTrgBOzviLeILkm7d3",
"createdAt": "2023-12-08T09:33:36Z",
"role": "user",
"message": "what is the meaning of life?"
},
{
"id": "msg_kTZonuiwZDxnipDADXk4bgFd",
"createdAt": "2023-12-08T09:33:41Z",
"role": "assistant",
"message": "The answer to the meaning of life is 42. It's a deep philosophical concept from \"The Hitchhiker's Guide to the Galaxy\" by Douglas Adams."
}
]
}
}
}
}
```
Something to note is that ChatGPT detected that this was a "deep question" that could be answered with the webhook `answer_deep_questions` so this is roughly what happened:
![answer deep questions sequence of events](/images/guides/ai/assistants/answer_deep_questions.png)
We can continue the conversation by performing from mutations. Notice this time we can ask graphite to only return new message by specifying `prevMessageID`:
```graphql
mutation {
graphite {
sendMessage(
sessionID:"thread_dxHHNpQryFpJ8wXXD9Q2fQJg",
message: "recommend a comedy movie for a rainy day with score higher than 80"
prevMessageID: "msg_kTZonuiwZDxnipDADXk4bgFd",
) {
sessionID
messages {
id
createdAt
role
message
}
}
}
}
```
Which should return something like:
```json
{
"data": {
"graphite": {
"sendMessage": {
"sessionID": "thread_dxHHNpQryFpJ8wXXD9Q2fQJg",
"messages": [
{
"id": "msg_xfq0nauRLEAAvelve2M48wlR",
"createdAt": "2023-12-08T09:37:07Z",
"role": "user",
"message": "recommend a comedy movie for a rainy day with score higher than 80"
},
{
"id": "msg_FbLWDnN9zPlsFtcQJYEpTnRY",
"createdAt": "2023-12-08T09:37:22Z",
"role": "assistant",
"message": "Here are a few comedy movies with a score higher than 80 that you might enjoy on a rainy day:\n\n1. Saint Frances\n Overview: A comedy-drama film that follows a struggling 34-year-old woman as she becomes the nanny for a six-year-old girl and navigates life's ups and downs.\n Score: 86\n\n2. My Life as a Zucchini\n Overview: After losing his mother, a young boy is sent to a foster home filled with other orphans, and he seeks to find love, laughter, and family amidst the challenges.\n Score: 84\n\n3. Paddington\n Overview: The movie follows the misadventures of a young bear from Peru who finds himself in London and is taken in by a London family.\n Score: 86\n\n4. In the Heights\n Overview: A musical-comedy drama set in the neighborhood of Washington Heights in New York City, where a bodega owner dreams of a better life.\n Score: 83\n\n5. The Grand Budapest Hotel\n Overview: This comedic adventure film takes place in a renowned European hotel and follows the escapades of its quirky characters, including a concierge and a lobby boy.\n Score: 86\n\nThese movies offer humor and heartwarming stories that are perfect for a cozy movie night. Enjoy watching!"
}
]
}
}
}
}
```
In this case ChatGPT detected some of the information needed to formulate the answer could be retrieved with the GraphQL query `GetMoviesWithScoreHigherThan` so it requested graphite to run the query and return the data. This is what happened:
![GetMoviesWithScoreHigherThan sequence of events](/images/guides/ai/assistants/GetMoviesWithScoreHigherThan.png)
## Implementing workflows
Interactions are not limited to just one tool or to simply query data. Tools can be combined and they can perform any action you wish. For instance, let's take a look to the following session:
```json
{
"data": {
"graphite": {
"sendDevMessage": {
"sessionID": "thread_kxgdq2wPDwdWETwGsC68psGw",
"messages": [
{
"id": "msg_KvVwu3ypYgIdwJhOU57NYoFh",
"role": "user",
"createdAt": "2023-12-21T08:16:03Z",
"message": ""
},
{
"id": "msg_orBzaQTulaWtFLe2VhgjX0op",
"role": "user",
"createdAt": "2023-12-21T08:16:18Z",
"message": "Are there any good comedies in space?"
},
{
"id": "msg_WatLEqiP8IyZYkImBsgJPtm0",
"role": "assistant",
"createdAt": "2023-12-21T08:16:29Z",
"message": "Here are some good comedies set in space:\n\n1. Linoleum - When the host of a failing childrens science show tries to fulfill his childhood dream of becoming an astronaut by building a rocket ship in his garage, a series of bizarre events occur that cause him to question his own reality.\n\n2. Spaceballs - When the nefarious Dark Helmet hatches a plan to snatch Princess Vespa and steal her planet's air, space-bum-for-hire Lone Starr and his clueless sidekick fly to the rescue. Along the way, they meet Yogurt, who puts Lone Starr wise to the power of \"The Schwartz.\" Can he master it in time to save the day?\n\nThese are great choices for a space comedy!"
},
{
"id": "msg_Jq8iO0EXHtb2x9Z9CLFgxzhT",
"role": "user",
"createdAt": "2023-12-21T08:17:09Z",
"message": "Spaceballs sounds like fun. Could you imagine a sequel?"
},
{
"id": "msg_2landwp0CiGuzhTuyInUjRFN",
"role": "assistant",
"createdAt": "2023-12-21T08:17:15Z",
"message": "Absolutely, a sequel to Spaceballs could be a lot of fun! In the original movie, the characters encounter the nefarious Dark Helmet and his diabolical plans. A sequel could explore new adventures, new villains, and wacky space escapades. It could also offer a fresh take on space-themed parodies and comedic twists. Fans of the original movie would surely be excited to see what new mischief Lone Starr, Princess Vespa, and the rest of the gang get into in a sequel."
},
{
"id": "msg_hVjfo6H0uJu3kX43zNhFMizl",
"role": "user",
"createdAt": "2023-12-21T08:18:36Z",
"message": "Now come up with a title and an overview and add it to the database and let me know the id returned"
},
{
"id": "msg_B75oj7QFtR8pubkBdp1rAI4a",
"role": "assistant",
"createdAt": "2023-12-21T08:18:54Z",
"message": "The movie \"Spaceballs 2: The Search for More Money\" has been successfully added to the database. The ID returned for the movie is \"1e2f9f95-34f0-4865-94fc-4f85bc93dd1a\". Get ready for a cosmic adventure that's twice the fun and twice the lunacy!"
}
]
}
}
}
}
```
In this session the following happened:
1. We asked about comedies in space, which prompted the AI Assistant to leverage tool `SearchMovies`, which leverages auto-embeddings to perform searches using natural language.
2. Then we asked the Assistant to imagine a sequel and insert it in the database, which prompted the AI assistant to leverage the tool `InsertMovie` to add it to the database.
True, to its word, the movie was added to the database successfully:
```json
{
"data": {
"movie": {
"id": "1e2f9f95-34f0-4865-94fc-4f85bc93dd1a",
"name": "Spaceballs 2: The Search for More Money",
"overview": "After successfully thwarting Dark Helmet's plans, Lone Starr and his crew find themselves in need of more cash. They embark on a hilarious and outrageous quest to find the elusive currency of the galaxy: spacebucks. Along the way, they encounter new foes, old friends, and a lot of absurdity. Get ready for a cosmic adventure that's twice the fun and twice the lunacy!",
"genre": "Comedy, Science Fiction"
}
}
}
```
And remember, this is just a tiny example of what tools can do. Tools can do anything you want; from working with data in your project to booking flights or selling your products to your users.
## User session
Interacting with graphite requires a user session (either an Authorization header or the admin secret). When interacting with the GraphQL endpoint or the webhook, graphite will re-use this session, which means that the webhook can use these headers to authorize the request. It also means that the GraphQL query will be performed as the user, so the user will have to have permissions to perform the query and will only get data the user has access to.
## Other GraphQL queries/mutation
In addition to the queries/mutations shown in this document there are other mutations for listing, deleting and updating assistants, sessions, etc. We recommend you to check the GraphQL schema to see what's available.
## Metadata and Permissions
For each assistant and session managed by graphite, a row in the tables `graphite.assistants` and `graphite.sessions` will be made available. The purpose of this metadata is to allow you to set permissions as with any other object in your project. For details on which permissions are needed for each specific query and mutation refer to the GraphQL documentation in the [reference](/reference/graphql/ai/overview) section.

View File

@@ -0,0 +1,186 @@
---
title: "Auto-Embeddings"
description: "Generate embeddings for your application automatically"
icon: arrow-right
---
Auto-emeddings leverages [pgvector](https://github.com/pgvector/pgvector) and [OpenAI](https://platform.openai.com/docs/guides/embeddings) to easily generate embeddings for your data in near real-time and without hassle. In addition, Auto-Embeddings allows you to seamlessly query your data using natural language or even using another object in your database (similarity search). Finally, the embeddings generated by Auto-Embeddings can be leveraged by your own code to perform any analysis or provide any extra functionality you may need.
## Generating embeddings
Embeddings generation works in the following way:
1. Every `sync-period` (5 minutes by default) graphite queries your data and searches for new data (or existing data that changed recently).
2. It extracts the relevant data for generating the embeddings
3. It requests OpenAI to generate embeddings
4. It stores the embeddings alongside the data.
## Querying embeddings
Once the embeddings have been generated you can leverage [pgvector](https://github.com/pgvector/pgvector) to perform any operation you may want. In addition, Auto-Embeddings will automatically provide two new queries; `graphiteSearchXXX` (search using natural language) and `graphiteSimilarXXX` (search for objects similar to a given object), where `XXX` is a name given by you.
## Demo
To demonstrate how to configure Auto-Embeddings we will use an example project where we are storing movies.
Before we start, let's start by explaining the project. Our project contains a single table called `movies` with the following columns:
![table](/images/guides/ai/auto-embeddings/table.png)
Our goal is going to be to generate embeddings using the data in the columns `name`, `genre` and `overview`.
### Preparing your database
Before we can start generating embeddings we need to prepare our database:
1. First we are going to need a column to store the embeddings.
2. Finally, we are going to need a mechanism to detect when embeddings need to be regenerated.
#### Embeddings Column
Creating a column to store embeddings is as easy as creating any other column. Just make sure it is of type `vector(1536)` and it can be `NULL`. For our project we can simply go to the SQL tab and create a migration with the following content:
```sql
-- in this example we are using "embeddings" as the name for our column
-- but you can choose anything you want
ALTER TABLE public.movies ADD COLUMN embeddings vector(1536) NULL;
```
For instance:
![creating embeddings column](/images/guides/ai/auto-embeddings/column.png)
#### Detecting Changes
On every `sync-period` graphite will perform a graphql query to get all the rows that have outdated embeddings. This means we can build this query in a way that:
1. Gets the data we need for the embeddings.
2. Retrieves objects with the embeddings column set to `NULL`.
3. Leverages another mechanism to detect which rows needs their embeddings regenerated.
In this example we are going to opt for the following mechanism to detect outdated embeddings:
1. Add a column `outdated` (boolean) to indicated whether the row is outdated or not.
2. Add a postgres trigger and function that will set the `outdated` column to true everytime there is a change to our data.
With this in mind we are going to create a migration with the following contents:
```sql
-- Add the "outdated" column of type "boolean" with a default value of true
ALTER TABLE "movies" ADD COLUMN "outdated" boolean DEFAULT true;
-- Create a trigger that sets "outdated" to true if the columns
-- "name", "genre" or "overview" are updated
CREATE OR REPLACE FUNCTION set_outdated_trigger()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.name <> OLD.name OR NEW.genre <> OLD.genre OR NEW.overview <> OLD.overview THEN
NEW.outdated := true;
ELSEIF NEW.embeddings IS NOT NULL THEN
NEW.outdated := false;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Attach the trigger to the table
CREATE TRIGGER set_outdated
BEFORE UPDATE ON "movies"
FOR EACH ROW
EXECUTE FUNCTION set_outdated_trigger();
```
For instance:
![outdated mechanism migration](/images/guides/ai/auto-embeddings/outdated.png)
Now, `graphite`'s query will be (more on this later):
```graphql
query GetOutdatedMovies {
movies(where: {
_or: [
{embeddings: {_is_null: true}}, # new rows without embeddings
{outdated: {_eq: true}, # existing rows with changed data
},
]}) {
id # id column is mandatory
name
genre
overview
}
}
```
<Note>This mechanism has the advantage that is simple enough and fully handled by postgres so you don't need to worry about it. In addition, it should be flexible enough to cover most cases. For very complex use-cases you could skip the postgres function and trigger and update the `outdated` directly from your application or you could use some completely different mechanism (i.e. a computed field). The important bit is that graphite needs to be able to make a graphql query and get the relevant rows and data.</Note>
### Configuring Auto-Embeddings
Now that we have prepared our database we can proceed to configure Auto-Embeddings. You will need the following data:
1. A unique name. We are going to use `movies` for this particular example but it can be anything. This will determine the name of the GraphQL queries `graphiteSearchXXX` and `graphiteSimilarXXX`.
2. The location of the embeddings column; schema, table and column names, in this example `public`, `movies` and `embeddings` respectively.
3. A GraphQL query to retrieve the outdated rows and their new data (the query we worked on in the previous section)
4. A GraphQL mutation that takes the `id` of the object, the embeddings and that updates the relevant object. For instance, in this particular example the following mutation would suffice:
```graphql
mutation UpdateEmbeddingsMovie($id: uuid!, $embeddings: vector) {
updateMovie(pk_columns: {id: $id}, _set: {embeddings: $embeddings}) {
__typename
}
}
```
NOTE: It is important that the query returns the id of the object and that the mutation accepts it as otherwise graphite won't know which object to update.
Now that we have all the data we need, adding the configuration is as simple as running the following graphql mutation:
<Tabs>
<Tab title="dashboard">
![configuration](/images/guides/ai/auto-embeddings/auto-embedding.png)
</Tab>
<Tab title="graphql">
```graphql
mutation {
insertGraphiteAutoEmbeddingsConfiguration(
object: {
name: "movies",
schemaName: "public",
tableName: "movies",
columnName: "embeddings",
query: "query GetOutdatedMovies { movies(where: { _or: [ {embeddings: {_is_null: true}}, {outdated: {_eq: true}}, ]}) { id name genre overview crew } }",
mutation: "mutation UpdateEmbeddingsMovie( $id: uuid!, $embeddings: vector, ) { updateMovie( pk_columns: {id: $id}, _set: { embeddings: $embeddings, }) { __typename } }",
}) {
id
}
}
```
</Tab>
</Tabs>
### Aftermath
#### Embeddings Generation
After executing the mutation above two things will happen; the first one is that if we look at our logs we will start seeing entries like this on the next `sync-period`:
![logs](/images/guides/ai/auto-embeddings/logs.png)
The logs indicate that graphite has started to generate embeddings for the movies. We can track the progress by counting movies with the `embeddings` column set to `NULL`:
![auto embeddings count non zero](/images/guides/ai/auto-embeddings/count_non_zero.png)
Until eventually it reaches 0.
![auto embeddings count 0](/images/guides/ai/auto-embeddings/count_0.png)
#### Natural Language and Similarity Search
The second thing that will happen is that the queries `graphiteSearchMovies`, `graphiteSearchMoviesAggregate`, `graphiteSimilarMovies` and `graphiteSimilarMoviesAggregate` will be created. These queries will work similar to the standard `movies` and `moviesAggregate` queries provided by hasura and will respect the same permissions but they will also allow you to query movies using natural language or other movies for comparison. For instance:
![auto-embeddings-search](/images/guides/ai/auto-embeddings/search.png)
![auto-embeddings-similar](/images/guides/ai/auto-embeddings/similar.png)

View File

@@ -0,0 +1,17 @@
---
title: "Developer Assistant"
description: "Empower your developers to code faster and better"
icon: hat-wizard
---
The developer assistant is a special AI Assistant enabled through the dashboard that has access to your project's schema and can help developing your project.
To use it simply [enable graphite](enabling-service) and query it through the dasbhoard:
![dashboard](/images/guides/ai/dev-assistants/dashboard.png)
![chat](/images/guides/ai/dev-assistants/chat.png)
## Permissions
Only admins are allowed to interact with the developer assistant.

View File

@@ -0,0 +1,68 @@
---
title: "Enabling Service"
icon: play
---
You can enable Graphite, Nhost's AI service, with the following steps:
<Steps>
<Step title="Check your database version">
Check your project's settings and make sure the database version is at least `14.6-20231018-1`. If it isn't upgrade your database version (latest available version is recommended).
<Tabs>
<Tab title="Dashboard">
![database settings](/images/guides/ai/enabling/database.png)
</Tab>
<Tab title="toml">
```toml
[postgres]
version = '14.6-20231018-1'
```
</Tab>
</Tabs>
</Step>
<Step title="Get an OpenAPI key">
Get an OpenAI API key from their [customer portal](https://platform.openai.com/account/api-keys).
![openai dashboard](/images/guides/ai/enabling/openai.png)
</Step>
<Step title="Configure the service">
Finally, you can head to your project's settings -> AI section and enable the service or, alternatively, use your TOML configuration file to enable the service:
<Tabs>
<Tab title="Dashboard">
![ai configuration screenshot](/images/guides/ai/enabling/settings.png)
</Tab>
<Tab title="toml">
```toml
[ai]
# Version of the service to use. Check the settings page for available versions
version = '0.3.1'
# Used to validate requests between postgres and the AI service.
# The AI service will also include the header X-Graphite-Webhook-Secret
# with this value set when calling external webhooks so the source of
# the request can be validated.
webhookSecret = '{{ secrets.GRAPHITE_WEBHOOK_SECRET }}'
[ai.autoEmbeddings]
# How often to run the job that keeps embeddings up to date
synchPeriodMinutes = 5
[ai.openai]
# Key to use for authenticating API requests to OpenAI.
apiKey = '{{ secrets.OPENAI_API_KEY }}'
# OpenAI organization to use.
organization = 'my-org'
[ai.resources.compute]
# Dedicated resources allocated for the service
cpu = 128
memory = 256
```
</Tab>
</Tabs>
</Step>
</Steps>

View File

@@ -0,0 +1,45 @@
---
title: "Local Development"
icon: code
---
If you are using the Nhost CLI for local development, as of [v0.12.0](https://github.com/nhost/cli/releases/tag/v1.12.0) you can also start Graphite locally. To do so, follow the next steps:
<Steps>
<Step title="Configuring the Service">
Follow the steps highlighed in the ["Enabling Service"](enabling-service) guide and don't forget to add the relevant secrets to your `.secrets` file.
</Step>
<Step title="Start nhost">
Run `nhost up`:
![nhost up](/images/guides/ai/local_development/nhost_up.png)
After starting the service the first thing you will notice is that there is a new `ai` service running.
</Step>
<Step title="Commit metadata changes">
As you start the AI service metadata changes may be proposed:
![git status](/images/guides/ai/local_development/git_status.png)
We strongly recommmend you to commit them to your git repository so they can be deployed alongside your application.
</Step>
</Steps>
### Synhcronizing Auto-Embeddings
If you add [auto-embeddings](/guides/ai/auto-embeddings) configuration locally and want to synchronize them with the cloud we recommend inserting them using a migration rather than with the auto-embeddings UI:
![migration](/images/guides/ai/local_development/migration.png)
And then running `nhost up` to download the updated metadata. Afterwards you should see both database migrations and functions' metadata changes in your local project:
![git status](/images/guides/ai/local_development/git_status_functions.png)
Pushing them to your deployment branch will also deploy them to your cloud project.
### Synhcronizing Assistants
Similar to auto-embeddings, if you want to synchronize [assistants](/guides/ai/assistants) we recommend you to insert them using a migration and then running `nhost up` to update any metadata if necessary. After pushing the proposed changes to the deployment branch all the changes should be deployed to the cloud project.

View File

@@ -1,17 +1,16 @@
---
title: 'Settings'
sidebar_position: 3
title: "Configuring Hasura"
description: "Learn how to configure your Hasura GraphQL Engine"
icon: gear
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
You can customize and tweak the configuration of the Hasura GraphQL Engine using your project's configuration file.
Below you can find the official schema (cue) and an example to configure your graphql service:
Below, you will find the official `CUE` schema and an example on how to configure your GraphQL service.
<Tabs groupId="package-manager">
<TabItem value="schema" label="schema">
## Available Settings
```cue
```cue Schema
// Configuration for hasura service
#Hasura: {
// Version of hasura, you can see available versions in the URL below:
@@ -61,10 +60,12 @@ Below you can find the official schema (cue) and an example to configure your gr
resources?: #Resources
}
```
</TabItem>
<TabItem value="toml" label="toml" default>
```toml
## Example Configuration
To configure your GraphQL instance, simply set the relevant settings under `[hasura]` in your project's configuration file.
```toml nhost.toml
[hasura]
version = ''
adminSecret = 'adminsecret'
@@ -96,17 +97,12 @@ replicas = 1
cpu = 500
memory = 1024
```
</TabItem>
</Tabs>
### JWT Secret
All formats supported by [hasura](https://hasura.io/docs/latest/auth/authentication/jwt/) should be supported:
Zooming in on `JWTSecret`.
<Tabs groupId="package-manager">
<TabItem value="schema" label="schema" default>
```cue
```cue Schema
#JWTSecret:
({
type: "HS384" | "HS512" | "RS256" | "RS384" | "RS512" | "Ed25519" | *"HS256"
@@ -143,10 +139,10 @@ All formats supported by [hasura](https://hasura.io/docs/latest/auth/authenticat
} & {
}
```
</TabItem>
<TabItem value="toml" label="toml">
```toml
## Example Configuration
```toml nhost.toml
# example 1
[[hasura.jwtSecrets]]
type = 'HS256'
@@ -170,5 +166,8 @@ claim = "x-other-claim"
path = "$.user.claim.id"
default = "default-value"
```
</TabItem>
</Tabs>
<Tip>
Read how [Authentication Using
JWTs](https://hasura.io/docs/latest/auth/authentication/jwt) works in Hasura
</Tip>

Some files were not shown because too many files have changed in this diff Show More