Skip to content

Commit

Permalink
docs(guides): Review getting started and CLI guide (#3028)
Browse files Browse the repository at this point in the history
  • Loading branch information
daffl authored Jan 31, 2023
1 parent 7b8250b commit 101da57
Show file tree
Hide file tree
Showing 20 changed files with 310 additions and 479 deletions.
18 changes: 2 additions & 16 deletions docs/.vitepress/config.sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,23 +80,12 @@ export default {
{
text: 'CLI',
collapsible: true,
collapsed: true,
collapsed: false,
items: [
{
text: 'Using the CLI',
text: '📖 Readme',
link: '/guides/cli/index.md'
},
{
text: 'Generate App',
link: '/guides/cli/generate-app.md'
}
]
},
{
text: 'App Structure',
collapsible: true,
collapsed: true,
items: [
{
text: '📂 config',
items: [
Expand Down Expand Up @@ -149,9 +138,6 @@ export default {
link: '/guides/cli/service.shared.md'
}
]
},
{
text: '📄 index'
}
]
},
Expand Down
Binary file modified docs/guides/basics/assets/generate-app.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/guides/basics/assets/generate-service.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed docs/guides/basics/assets/github-keys.png
Binary file not shown.
12 changes: 8 additions & 4 deletions docs/guides/basics/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,9 +172,7 @@ You can find your existing applications in the [GitHub OAuth apps developer sett

</BlockQuote>

Once you've clicked "Register application", we need to update our Feathers app configuration with the client id and secret copied from the GitHub application settings:

![Screenshot of the created GitHub application client id and secret](./assets/github-keys.png)
Once you've clicked "Register application", we need to update our Feathers app configuration with the client id and secret copied from the GitHub application settings.

Find the `authentication` section in `config/default.json` replace the `<Client ID>` and `<Client Secret>` in the `github` section with the proper values:

Expand All @@ -193,6 +191,12 @@ Find the `authentication` section in `config/default.json` replace the `<Client
}
```

<BlockQuote type="info" label="note">

In a production environment you would set those values as [custom environment variables](../cli/custom-environment-variables.md).

</BlockQuote>

This tells the OAuth strategy to redirect back to our index page after a successful login and already makes a basic login with GitHub possible. Because of the changes we made in the `users` service in the [services chapter](./services.md) we do need a small customization though. Instead of only adding `githubId` to a new user when they log in with GitHub we also include their email and the avatar image from the profile we get back. We can do this by extending the standard OAuth strategy and registering it as a GitHub specific one and overwriting the `getEntityData` method:

Update `src/authentication.ts` as follows:
Expand Down Expand Up @@ -253,4 +257,4 @@ It will redirect to GitHub and ask to authorize our application. If everything w

## What's next?

Sweet! We now have an API that can register new users with email/password. This means we have everything we need for a frontend for our chat application. You have your choice of
Sweet! We now have an API that can register new users with email/password and GitHub. This means we have everything we need for a frontend for our chat application. See the [JavaScript frontend guide](../frontend/javascript.md) on how to create a plain JavaScript chat application.
29 changes: 15 additions & 14 deletions docs/guides/basics/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,22 +38,22 @@ Now update `src/hooks/log-runtime.js` as follows:

</LanguageBlock>

```ts{4-9}
```ts{2,5-10}
import type { HookContext, NextFunction } from '../declarations'
import { logger } from '../logger'
export const logRuntime = async (context: HookContext, next: NextFunction) => {
const startTime = Date.now()
// Run everything else (other hooks and service call)
await next()
const duration = Date.now() - startTime
console.log(
`Calling ${context.method} on ${context.path} took ${duration}ms`
)
logger.info(`Calling ${context.method} on ${context.path} took ${duration}ms`)
}
```

In this hook, we store the start time and then run all other hooks and the service method by calling `await next()`. After that we can calculate the duration in milliseconds by subtracting the start time from the current time and log the information to the console.
In this hook, we store the start time and then run all other hooks and the service method by calling `await next()`. After that we can calculate the duration in milliseconds by subtracting the start time from the current time and log the information using the application [logger](../cli/logger.md).

## Hook functions

Expand All @@ -78,8 +78,8 @@ Writeable properties are:
- `context.params` - The service method call `params`. For external calls, `params` usually contains:
- `context.params.query` - The query filter (e.g. from the REST query string) for the service call
- `context.params.provider` - The name of the transport the call has been made through. Usually `"rest"` or `"socketio"`. Will be `undefined` for internal calls.
- `context.params.user` - *If authenticated*, the data of the user making the service method call.
- `context.id` - The `id` of the record if the service method call is a `get`, `remove`, `update` or `patch`
- `context.params.user` - _If authenticated_, the data of the user making the service method call.
- `context.id` - The `id` of the record if the service method call is a `get`, `remove`, `update` or `patch`
- `context.data` - The `data` sent by the user in a `create`, `update` and `patch` and custom service method call
- `context.error` - The error that was thrown (in `error` hooks)
- `context.result` - The result of the service method call (available after calling `await next()` or in `after` hooks)
Expand All @@ -92,7 +92,7 @@ For more information about the hook context see the [hooks API documentation](..

## Registering hooks

In a Feathers application generated by the CLI, hooks are being registered in the `<servicename>.ts` file. The hook registration object is an object with `{ around, before, after, error }` and a list of hooks per method like `{ all: [], find: [], create: [] }`.
In a Feathers application, hooks are being registered in the [&lt;servicename&gt;](../cli/service.md) file. The hook registration object is an object with `{ around, before, after, error }` and a list of hooks per method like `{ all: [], find: [], create: [] }`.

<LanguageBlock global-id="ts">

Expand All @@ -105,7 +105,7 @@ To log the runtime of all `messages` service calls we can update `src/services/m

</LanguageBlock>

```ts{19,37}
```ts{20,38}
// For more information about this file see https://dove.feathersjs.com/guides/cli/service.html
import { authenticate } from '@feathersjs/authentication'
Expand All @@ -124,6 +124,7 @@ import {
import type { Application } from '../../declarations'
import { MessageService, getOptions } from './messages.class'
import { messagePath, messageMethods } from './messages.shared'
import { logRuntime } from '../../hooks/log-runtime'
export * from './messages.class'
Expand All @@ -132,14 +133,14 @@ export * from './messages.schema'
// A configure function that registers the service and its hooks via `app.configure`
export const message = (app: Application) => {
// Register our service on the Feathers application
app.use('messages', new MessageService(getOptions(app)), {
app.use(messagePath, new MessageService(getOptions(app)), {
// A list of all methods this service exposes externally
methods: ['find', 'get', 'create', 'patch', 'remove'],
methods: messageMethods,
// You can add additional custom events to be sent to clients here
events: []
})
// Initialize hooks
app.service('messages').hooks({
app.service(messagePath).hooks({
around: {
all: [
logRuntime,
Expand Down Expand Up @@ -168,12 +169,12 @@ export const message = (app: Application) => {
// Add this service to the service type index
declare module '../../declarations' {
interface ServiceTypes {
messages: MessageService
[messagePath]: MessageService
}
}
```

Now every time a our messages service is accessed successfully, the name, method and runtime will be logged.
Now every time our messages service is accessed successfully, the name, method and runtime will be logged.

<BlockQuote type="tip">

Expand Down
34 changes: 24 additions & 10 deletions docs/guides/basics/schemas.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ Update the `src/services/messages/messages.schema.js` file like this:

<DatabaseBlock global-id="sql">

```ts{2,8,15-17,24-27,39-45,58-61}
```ts{2,8,15-17,24-27,39-45,58-61,74-82}
// For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html
import { resolve, virtual } from '@feathersjs/schema'
import { Type, getValidator, querySyntax } from '@feathersjs/typebox'
Expand Down Expand Up @@ -332,14 +332,24 @@ export const messageQuerySchema = Type.Intersect(
)
export type MessageQuery = Static<typeof messageQuerySchema>
export const messageQueryValidator = getValidator(messageQuerySchema, queryValidator)
export const messageQueryResolver = resolve<MessageQuery, HookContext>({})
export const messageQueryResolver = resolve<MessageQuery, HookContext>({
userId: async (value, user, context) => {
// We want to be able to find all messages but
// only let a user modify their own messages otherwise
if (context.params.user && context.method !== 'find') {
return context.params.user.id
}
return value
}
})
```

</DatabaseBlock>

<DatabaseBlock global-id="mongodb">

```ts{2,8,15-17,24-27,39-45,58-61}
```ts{2,8,15-17,24-27,39-45,58-61,74-82}
// For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html
import { resolve, virtual } from '@feathersjs/schema'
import { Type, getValidator, querySyntax } from '@feathersjs/typebox'
Expand Down Expand Up @@ -409,7 +419,17 @@ export const messageQuerySchema = Type.Intersect(
)
export type MessageQuery = Static<typeof messageQuerySchema>
export const messageQueryValidator = getValidator(messageQuerySchema, queryValidator)
export const messageQueryResolver = resolve<MessageQuery, HookContext>({})
export const messageQueryResolver = resolve<MessageQuery, HookContext>({
userId: async (value, user, context) => {
// We want to be able to find all messages but
// only let a user modify their own messages otherwise
if (context.params.user && context.method !== 'find') {
return context.params.user._id
}
return value
}
})
```

</DatabaseBlock>
Expand All @@ -426,12 +446,6 @@ The `virtual()` in the `messageResolver` `user` property is a [virtual property]

Now that our schemas and resolvers have everything we need, we also have to update the database with those changes. For SQL databases this is done with migrations. Migrations are a best practice for SQL databases to roll out and undo changes to the data model. Every change we make in a schema will need its corresponding migration step.

<BlockQuote type="warning">

If you choose MongoDB you do **not** need to create a migration.

</BlockQuote>

Initially, every database service will automatically add a migration that creates a table for it with an `id` and `text` property. Our users service also already added a migration to add the email and password fields for logging in. The migration for the changes we made in this chapter needs to

- Add the `avatar` string field to the `users` table
Expand Down
5 changes: 2 additions & 3 deletions docs/guides/basics/services.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,13 @@ In general, a service is an object or instance of a class that implements certai
- Reading and/or writing to a completely different type of database
- Anything else you can think of!

This standardized interface allows us to interact with the Database/API/Gnomes inside in a uniform manner across any transport protocol, be it REST, websockets, internally within the application, or Carrier Pigeon.
This standardized interface allows us to interact with the Database/API/Gnomes inside in a uniform manner across any transport protocol, be it REST, websockets, internally within the application, or Carrier Pigeon 🕊️

Once you write a service method, it can then automatically be used as a REST endpoint or called by a websocket. Feathers will take care of all the boilerplate.

### Service methods

Service methods are [CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) methods that a service can implement.
Feathers offers a set of general methods that a service can implement, these are:
Service methods are [CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) methods that a service can implement. Feathers offers a set of general methods that a service can implement, these are:

- `find` - Find all data (potentially matching a query)
- `get` - Get a single data entry by its unique identifier
Expand Down
6 changes: 5 additions & 1 deletion docs/guides/cli/app.test.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,8 @@ outline: deep

# Application tests

> This page is currently a work in progress
The `app.test` file starts the server and then tests that it shows the index page and that 404 (Not Found) JSON errors are being returned. It uses the [Axios HTTP](https://axios-http.com/) library to make the calls.

This file can e.g. be used to test application [setup](../../api/application.md#setupserver) and [teardown](../../api/application.md#teardownserver).

All tests are using [MochaJS](https://mochajs.org/) but will be moving to the [NodeJS test runner](https://nodejs.org/api/test.html) in the future.
37 changes: 0 additions & 37 deletions docs/guides/cli/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,43 +28,6 @@ export const authentication = (app: Application) => {
}
```

## Client tests

If you selected a local strategy, `src/client.ts` will be updated with a client side integration test that looks similar to this:

```ts
it('creates and authenticates a user with email and password', async () => {
const userData: userData = {
email: 'someone@example.com',
password: 'supersecret'
}

await client.service('users').create(userData)

const { user, accessToken } = await client.authenticate({
strategy: 'local',
...userData
})

assert.ok(accessToken, 'Created access token for user')
assert.ok(user, 'Includes user in authentication data')
assert.strictEqual(user.password, undefined, 'Password is hidden to clients')

await client.logout()

// Remove the test user on the server
await app.service('users').remove(user.id)
})
```

This test will create a new user with the generated client, log in, verify a user was returned and log out again. To keep the test self-contained it will then remove the test user on the server

<BlockQuote type="tip">

Note that you can use `client` for client side interactions and the server side [application](./app.md#application) `app` object for server side calls in the same file. For example, if the user required an email verification but you don't want to test sending out emails you can call something like `app.service('users').patch(user.id, { verified: true })` to enable the new user on the server.

</BlockQuote>

## oAuth

Note that when selecting oAuth logins (Google, Facebook, GitHub etc.), the standard registered oAuth strategy only uses the `<name>Id` property to create a new user. This will fail validation against the default user [schema](./service.schemas.md) which requires an `email` property to exist. If the provider (and user) allows fetching the email, you can customize the oAuth strategy like shown for GitHub in the [oAuth authentication guide](../basics/authentication.md#login-with-github). You can also make the email in the schema optional with `email: Type.Optional(Type.String())`.
39 changes: 38 additions & 1 deletion docs/guides/cli/client.test.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,40 @@
# Client test

> This page is currently a work in progress
The `client.test` file contains end-to-end integration tests for the [generated client](./client.md).

## Authentication

If you selected a local strategy, `src/client.ts` will be updated with a client side integration test that looks similar to this:

```ts
it('creates and authenticates a user with email and password', async () => {
const userData: userData = {
email: 'someone@example.com',
password: 'supersecret'
}

await client.service('users').create(userData)

const { user, accessToken } = await client.authenticate({
strategy: 'local',
...userData
})

assert.ok(accessToken, 'Created access token for user')
assert.ok(user, 'Includes user in authentication data')
assert.strictEqual(user.password, undefined, 'Password is hidden to clients')

await client.logout()

// Remove the test user on the server
await app.service('users').remove(user.id)
})
```

This test will create a new user with the generated client, log in, verify a user was returned and log out again. To keep the test self-contained it will then remove the test user on the server

<BlockQuote type="tip">

Note that you can use `client` for client side interactions and the server side [application](./app.md#application) `app` object for server side calls in the same file. For example, if the user required an email verification but you don't want to test sending out emails you can call something like `app.service('users').patch(user.id, { isVerified: true })` to enable the new user on the server.

</BlockQuote>
4 changes: 3 additions & 1 deletion docs/guides/cli/default.json.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ outline: deep

# Application configuration

A generated application uses the **[configuration module](../../api/configuration.md)** to load configuration information based on the environment. It is based on the battle-tested and widely used [node-config](https://github.com/node-config/node-config) and loads configuration settings so that they are available via [app.get()](../../api/application.md#getname).
A generated application uses the **[configuration module](../../api/configuration.md)** to load configuration information based on the environment. It is based on the battle-tested and widely used [node-config](https://github.com/node-config/node-config) and loads configuration settings so that they are available via [app.get()](../../api/application.md#getname). On application startup, the configuration will be validated against the [configuration schema](./configuration.md).

<BlockQuote type="warning" label="Important">

Expand Down Expand Up @@ -70,6 +70,8 @@ Depending on the SQL database selected the `<database>` setting contains a `conn
}
```

For additional configuration see the [database connection guide](./databases.md#connection).

</DatabaseBlock>

<DatabaseBlock global-id="mongodb">
Expand Down
Loading

0 comments on commit 101da57

Please sign in to comment.