{"id":18895872,"url":"https://github.com/echobind/bisonapp-nextauth","last_synced_at":"2025-08-10T07:16:15.402Z","repository":{"id":98471873,"uuid":"395653153","full_name":"echobind/bisonapp-nextauth","owner":"echobind","description":null,"archived":false,"fork":false,"pushed_at":"2021-08-13T19:16:30.000Z","size":285,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-05-31T17:51:26.612Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/echobind.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-08-13T12:49:42.000Z","updated_at":"2022-03-09T14:05:14.000Z","dependencies_parsed_at":"2023-06-01T21:45:47.923Z","dependency_job_id":null,"html_url":"https://github.com/echobind/bisonapp-nextauth","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/echobind/bisonapp-nextauth","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/echobind%2Fbisonapp-nextauth","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/echobind%2Fbisonapp-nextauth/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/echobind%2Fbisonapp-nextauth/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/echobind%2Fbisonapp-nextauth/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/echobind","download_url":"https://codeload.github.com/echobind/bisonapp-nextauth/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/echobind%2Fbisonapp-nextauth/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":269690649,"owners_count":24459876,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-10T02:00:08.965Z","response_time":71,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-08T08:30:56.279Z","updated_at":"2025-08-10T07:16:15.360Z","avatar_url":"https://github.com/echobind.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\" style=\"text-align:center\"\u003e\n  \u003ch1\u003eMyBisonNextAuthApp\u003c/h1\u003e\n  \u003cp\u003e\u003cimg alt=\"CI STATUS\" src=\"https://github.com/echobind/bisonapp-nextauth/workflows/main/badge.svg\"/\u003e\u003c/p\u003e\n\u003c/p\u003e\n\n# Getting Started Tutorial\nThis checklist and mini-tutorial will make sure you make the most of your shiny new Bison app.\n\n## Migrate your database, generate typings, and start the dev server\n- [ ] Run `yarn setup:dev` to prep and migrate your local database, as well as generate the prisma client, nexus typings, and GraphQL typings. If this fails, make sure you have Postgres running and the generated `DATABASE_URL` values are correct in your `.env` files.\n- [ ] Run `yarn dev` to start your development server\n\n## Complete a Bison workflow\nWhile not a requirement, Bison works best when you start development with the database and API layer. We will illustrate how to use this by adding the concept of an organization to our app.\n\n### The Database\nBison uses Prisma for database operations. We've added a few conveniences around the default Prisma setup, but if you're familiar with Prisma, you're familiar with databases in Bison.\n\n- [ ] Define an Organization table in `prisma/schema.prisma`.\n\nWe suggest copying the `id`, `createdAt` and `updatedAt` fields from the `User` model.\n```\nmodel Organization {\n  id        String   @id @default(cuid())\n  name      String\n  users     User[]\n  createdAt DateTime @default(now())\n  updatedAt DateTime @updatedAt\n}\n```\n\nIf you use VSCode and have the [Prisma extension](https://marketplace.visualstudio.com/items?itemName=Prisma.prisma) installed, saving the file should automatically add the inverse relationship to the `User` model!\n\n```\nmodel User {\n  id             String        @id @default(cuid())\n  email          String        @unique\n  password       String\n  roles          Role[]\n  profile        Profile?\n  createdAt      DateTime      @default(now())\n  updatedAt      DateTime      @updatedAt\n  organization   Organization? @relation(fields: [organizationId], references: [id])\n  organizationId String?\n}\n```\n\n- [ ] Generate a migration with `yarn g:migration`.\n\nYou should see a new folder in `prisma/migrations`. We recommend opening the `README.md` file within the new migration folder to double check the sql that was generated for you.\n- [ ] Migrate the database with `yarn db:migrate`\n\nFor more on Prisma, [view the docs](https://www.prisma.io/docs/).\n\n### The GraphQL API\nWith the database changes complete, we need to decide what types, queries, and mutations to expose in our GraphQL API.\n\nBison uses [Nexus Schema](https://nexusjs.org/docs/) to create the GraphQL API. Nexus provides a strongly-typed, concise way of defining GraphQL types and operations.\n\n- [ ] Create a new GraphQL module using `yarn g:graphql organization`\n- [ ] Edit the new module to reflect what you want to expose via the API. In the following Mutation example, we alias the Mutation name, require a user to be logged in, and force the new Organization to be owned by the logged in user. All in about 10 lines of code!\n\nBecause Nexus is strongly typed, all of the `t.` operations should autocomplete in your editor. Bison uses the [Prisma plugin](https://nexusjs.org/docs/pluginss/prisma/api) for Nexus, which enables the `t.model` and `t.crud` functions.\n\n```ts\n// Organization Type\nexport const Organization = objectType({\n  name: 'Organization',\n  description: 'A Organization',\n  definition(t) {\n    t.model.id();\n    t.model.name();\n    t.model.createdAt();\n    t.model.updatedAt();\n  },\n});\n\n// Mutatations\nexport const OrganizationMutations = extendType({\n  type: 'Mutation',\n  definition(t) {\n    t.crud.createOneOrganization({\n      alias: 'createOrganization',\n      authorize: (_root, _args, ctx) =\u003e !!ctx.user,\n      computedInputs: {\n        users: ({ ctx }) =\u003e ({\n          connect: [{ id: ctx.user.id }],\n        }),\n      },\n    });\n  },\n});\n```\n\n### Understanding the GraphQL API and TypeScript types\n- [ ] Open `api.graphql` and look at our the new definitions that were generated for you:\n\n```graphql\n\"\"\"\nAn Organization\n\"\"\"\ntype Organization {\n  createdAt: DateTime!\n  id: String!\n  updatedAt: DateTime!\n}\n\ninput OrganizationCreateOneWithoutUsersInput {\n  connect: OrganizationWhereUniqueInput\n  create: OrganizationCreateWithoutUsersInput\n}\n\ninput OrganizationCreateWithoutUsersInput {\n  createdAt: DateTime\n  id: String\n  updatedAt: DateTime\n}\n\ninput OrganizationWhereInput {\n  AND: [OrganizationWhereInput!]\n  createdAt: DateTimeFilter\n  id: StringFilter\n  NOT: [OrganizationWhereInput!]\n  OR: [OrganizationWhereInput!]\n  updatedAt: DateTimeFilter\n  users: UserListRelationFilter\n}\n\ninput OrganizationWhereUniqueInput {\n  id: String\n}\n```\n\n- [ ] Open up `types.ts` to see the generated TypeScript types that correspond with the graphql changes.\n\n### API Request Tests\nLet's confirm the API changes using a request test. To do this:\n- [ ] Generate a new factory: `yarn g:test:factory organization`\n- [ ] Add a default value for organization name in the build function. You can use any of the methods from the `chance` library.\n\n```ts\nexport const OrganizationFactory = {\n  build: (attrs: Partial\u003cOrganization\u003e = {}) =\u003e {\n    return {\n      id: chance.guid(),\n      name: chance.company(), // \u003c-- add this\n      ...attrs,\n    };\n  },\n// ...\n```\n\n- [ ] Generate a new api request test: `yarn g:test:request createOrganization`\n- [ ] Update the API request test to call the new mutation and ensure that we get an error if not logged in. If you are curious what the `Input` type should be, check `api.graphql`.\n\nHere we use inline snapshots to confirm the error message content, but you can also manually assert the content.\n\n```ts\nimport { graphQLRequest, graphQLRequestAsUser, resetDB, disconnect } from '../../helpers';\nimport { OrganizationFactory } from '../factories/organization';\n\nbeforeEach(async () =\u003e resetDB());\nafterAll(async () =\u003e disconnect());\n\ndescribe('createOrganization mutation', () =\u003e {\n  it('returns an error if not logged in', async () =\u003e {\n    const query = `\n    mutation createOrganization($data: OrganizationCreateInput!) {\n      createOrganization(data: $data) {\n        id\n        name\n        users {\n          email\n        }\n      }\n    }\n  `;\n\n    const variables = { data: { name: 'Cool Company' } };\n    const response = await graphQLRequest({ query, variables });\n    const errorMessages = response.body.errors.map((e) =\u003e e.message);\n\n    expect(errorMessages).toMatchInlineSnapshot(`\n        Array [\n          \"Not authorized\",\n        ]\n      `);\n  });\n});\n```\n\n- [ ] add a new test to confirm that the organization user is set to the current user\n\n```ts\nit('sets the user to the logged in user', async () =\u003e {\n    const query = `\n    mutation createOrganization($data: OrganizationCreateInput!) {\n      createOrganization(data: $data) {\n        id\n        name\n        users {\n          id\n        }\n      }\n    }\n  `;\n\n    const user = await UserFactory.create();\n    const variables = { data: { name: 'Cool Company', users: { connect: [{ id: 'notmyid' }] } } };\n    const response = await graphQLRequestAsUser(user, { query, variables });\n    const organization = response.body.data.createOrganization;\n    const [organizationUser] = organization.users;\n\n    expect(organizationUser.id).toEqual(user.id);\n  });\n```\n\n### Add a Frontend page and form that creates an organization\nNow that we have the API finished, we can move to the frontend changes.\n\n- [ ] Create a new page to create organizations: `yarn g:page organizations/new`\n- [ ] Create an OrganizationForm component: `yarn g:component OrganizationForm`\n- [ ] Add a simple form with a name input. See the [React Hook Form docs](https://react-hook-form.com) for detailed information.\n\n```tsx\nimport React from 'react';\nimport { useForm } from 'react-hook-form';\n\n\nexport function OrganizationForm() {\n  const { register, handleSubmit, errors } = useForm();\n\n  async function onSubmit(data) {\n    console.log(data)\n  }\n\n  return (\n    \u003cform onSubmit={handleSubmit(onSubmit)}\u003e\n      \u003cinput {...register('name', { required: true })} /\u003e\n      {errors.name \u0026\u0026 \u003cspan\u003eThis field is required\u003c/span\u003e}\n\n      \u003cinput type=\"submit\" /\u003e\n    \u003c/form\u003e\n  );\n}\n```\n\n- [ ] Update the form to use Chakra components\n\n```tsx\n  return (\n    \u003cform onSubmit={handleSubmit(onSubmit)}\u003e\n      \u003cFormControl id=\"name\"\u003e\n        \u003cFormLabel htmlFor=\"name\"\u003eName\u003c/FormLabel\u003e\n        \u003cInput\n          type=\"text\"\n          {...register('name', { required: true })}\n          isInvalid={errors.name}\n        /\u003e\n        \u003cErrorText\u003e{errors.name \u0026\u0026 errors.name.message}\u003c/ErrorText\u003e\n      \u003c/FormControl\u003e\n\n      \u003cButton type=\"submit\" marginTop={8} width=\"full\"\u003e\n        Signup\n      \u003c/Button\u003e\n    \u003c/form\u003e\n  );\n}\n```\n\n- [ ] Add a graphql mutation to create an organization (use the same code from the API request test to keep it easy!)\n- [ ] Make sure you import gql from @apollo/client since we are working in the frontend.\n\n```tsx\n// add the mutation and save the file\nexport const CREATE_ORGANIZATION_MUTATION = gql`\n  mutation createOrganization($data: OrganizationCreateInput!) {\n    createOrganization(data: $data) {\n      id\n      name\n      users {\n        id\n      }\n    }\n  }\n`;\n```\n\n- [ ] Save the file. You should see GraphQL Codegen pickup on the changes.\n- [ ] Open `types.ts`. Codegen should have created a new hook called `useCreateOrganizationMutation`, which we can use to get fully typed graphql operations!\n\n```tsx\n// types.ts - search for the following function:\n\nexport function useCreateOrganizationMutation(\n  baseOptions?: ApolloReactHooks.MutationHookOptions\u003c\n    CreateOrganizationMutation,\n    CreateOrganizationMutationVariables\n  \u003e\n)\n```\n\n- [ ] Use the newly generated hook to save the results of the form:\n\n```tsx\nexport function OrganizationForm() {\n  const { register, handleSubmit, errors } = useForm();\n  const [createOrganization, { data, loading, error }] = useCreateOrganizationMutation();\n\n  async function onSubmit(data) {\n    createOrganization(data);\n  }\n}\n```\n\n- [ ] Attach the mutations loading state to the button loading state\n\n```tsx\n\u003cButton type=\"submit\" marginTop={8} width=\"full\" isLoading={loading}\u003e\n  Signup\n\u003c/Button\u003e\n```\n\nYou should now have a fully working form that creates a new database entry on submit!\n\n### Adding a new page that shows the organization\n\n- [ ] Generate a new page: `yarn g:page organizations/[:id]`. This uses the dynamic page capability of Next.js.\n- [ ] Add a new \"cell\" to fetch data. While not required, it keeps things clean. `yarn g:cell Organization`\n- [ ] Add a query to the cell that fetches organization data\n\n```jsx\nimport React from 'react';\nimport gql from 'graphql-tag';\nimport { Spinner, Text } from '@chakra-ui/react';\n\nexport const QUERY = gql`\n  query organization {\n    organization {\n      name\n    }\n  }\n`;\n\nexport const Loading = () =\u003e \u003cSpinner /\u003e;\nexport const Error = () =\u003e \u003cText\u003eError. See dev tools.\u003c/Text\u003e;\nexport const Empty = () =\u003e \u003cText\u003eNo data.\u003c/Text\u003e;\n\nexport const Success = () =\u003e {\n  return \u003cText\u003eAwesome!\u003c/Text\u003e;\n};\n\nexport const OrganizationCell = () =\u003e {\n  return \u003cEmpty /\u003e;\n};\n```\n\n- [ ] Verify we get an error about querying for organizations in the dev server console.\n\n```\n[WATCHERS] [GQLCODEGEN]     GraphQLDocumentError: Cannot query field \"organization\" on type \"Query\".\n```\n\n- [ ] Fix this by allowing users to query by organization. Update the `graphql/modules/organzization` file.\n\n```ts\n// add this\nexport const OrganizationQueries = extendType({\n  type: 'Query',\n  definition(t) {\n    t.crud.organization();\n  },\n});\n```\n\nGoing back to our dev console, we should see a new error.\n\n```\n[WATCHERS] [GQLCODEGEN]     GraphQLDocumentError: Field \"organization\" argument \"where\" of type \"OrganizationWhereUniqueInput!\" is required, but it was not provided.\n```\n\nWe forgot to add a where clause to our organization query that's in the cell. Let's do that now.\n\n- [ ] Open `api.graphql` to see the parameters we can pass to the organization query.\n- [ ] Copy the `where` parameter and use it in our cell.\n\n```graphql\ntype Query {\n  '''\n  organization(where: OrganizationWhereUniqueInput!): Organization\n  '''\n}\n```\n\n- [ ] Update the organization query to take a parameter and use the where query\n\n```tsx\nexport const QUERY = gql`\n  query organization($where: OrganizationWhereUniqueInput!) {\n    organization(where: $where) {\n      name\n    }\n  }\n`;\n```\n\n- [ ] Use the newly generated hook from `types.ts` to fetch data in the cell.\n- [ ] Add a prop to the cell for organizationId and pass the value to the query.\n- [ ] Udate the Success component to take the proper return type for the query\n- [ ] Only render the Success component if `data.organization` is present.\n\n```tsx\nimport React from 'react';\nimport gql from 'graphql-tag';\nimport { Spinner, Text } from '@chakra-ui/react';\n\nimport { OrganizationQuery, useOrganizationQuery } from '../types';\n\nexport const QUERY = gql`\n  query organization($where: OrganizationWhereUniqueInput!) {\n    organization(where: $where) {\n      name\n    }\n  }\n`;\n\nexport const Loading = () =\u003e \u003cSpinner /\u003e;\nexport const Error = () =\u003e \u003cText\u003eError. See dev tools.\u003c/Text\u003e;\nexport const Empty = () =\u003e \u003cText\u003eNo data.\u003c/Text\u003e;\n\nexport const Success = ({ organization }: OrganizationQuery) =\u003e {\n  return \u003cText\u003eAwesome! {organization.name}\u003c/Text\u003e;\n};\n\nexport const OrganizationCell = ({ organizationId }) =\u003e {\n  const { data, loading, error } = useOrganizationQuery({\n    variables: {\n      where: { id: organizationId },\n    },\n  });\n\n  if (loading) return \u003cLoading /\u003e;\n  if (error) return \u003cError /\u003e;\n  if (data.organization) return \u003cSuccess {...data} /\u003e;\n\n  return \u003cEmpty /\u003e;\n};\n```\n\n- [ ] Add the Cell to the organization page:\n\n```tsx\nimport React from 'react';\nimport Head from 'next/head';\nimport { Flex } from '@chakra-ui/react';\nimport { useRouter } from 'next/router';\n\nimport { OrganizationCell } from '../../cells/OrganizationCell';\n\nfunction OrganizationPage() {\n  const router = useRouter();\n  const { id } = router.query;\n\n  return (\n    \u003c\u003e\n      \u003cHead\u003e\n        \u003ctitle\u003eAn organization\u003c/title\u003e\n      \u003c/Head\u003e\n\n      \u003cFlex direction={{ base: 'column', lg: 'row' }}\u003e\n        \u003cOrganizationCell organizationId={id} /\u003e\n      \u003c/Flex\u003e\n    \u003c/\u003e\n  );\n}\n\nexport default OrganizationPage;\n```\n\n## Congrats!\n\nOutside of e2e tests, you've used just about every feature in Bison. But don't worry. We've got your back there too.\n\nBonus:\n\n- [ ] View the login and logout e2e tests\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fechobind%2Fbisonapp-nextauth","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fechobind%2Fbisonapp-nextauth","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fechobind%2Fbisonapp-nextauth/lists"}