Skip to content

Decouple ui/* from api/* via openapi types#4725

Closed
KrumTy wants to merge 29 commits into
fe/feature/RI-7039-replace-euifrom
feature/resolve-ts-issues
Closed

Decouple ui/* from api/* via openapi types#4725
KrumTy wants to merge 29 commits into
fe/feature/RI-7039-replace-euifrom
feature/resolve-ts-issues

Conversation

@KrumTy
Copy link
Copy Markdown
Contributor

@KrumTy KrumTy commented Jul 16, 2025

Issue

Currently we have a lot of typescript issues on the client side. Many of them are due to references to the nestjs api and/or mismatch between api contracts.

What this PR does

It addresses decoupling the ui/* from api/* be leveraging auto generated types based on openapi (swagger).
It shrinks the tsc errors by about 50% (from ~2500 to ~1300) and sets the path for further ts fixes.

Review guide

This PR appears large at first but that's because of the large amount of autogenerated types. Do not review the ui/src/api-client folder

Notes

  • When an api controller changes (param/dtos/etc), generate-client script needs to be rerun in order to generate the new types for the client. Not sure if this can be automated.
  • There's an option to split the autogenerated types into multiple files (one per type) but searching through the codebase with so many overlapping file names becomes too hard, so I kept them in one (api/clinet/api.ts)

@KrumTy KrumTy changed the title Feature/resolve ts issues Decouple ui/ from api/ via openapi types Jul 16, 2025
@KrumTy KrumTy changed the title Decouple ui/ from api/ via openapi types Decouple ui/* from api/* via openapi types Jul 16, 2025
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jul 16, 2025

Code Coverage - Integration Tests

Status Category Percentage Covered / Total
🟢 Statements 81.76% 16250/19874
🟡 Branches 64.83% 7333/11310
🟡 Functions 70.87% 2280/3217
🟢 Lines 81.4% 15283/18774

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jul 16, 2025

Code Coverage - Backend unit tests

St.
Category Percentage Covered / Total
🟢 Statements 92.36% 13788/14928
🟡 Branches 74% 4143/5599
🟢 Functions 85.96% 2124/2471
🟢 Lines 92.15% 13178/14300

Test suite run success

2940 tests passing in 286 suites.

Report generated by 🧪jest coverage report action from 5c05cfb

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jul 16, 2025

Code Coverage - Frontend unit tests

St.
Category Percentage Covered / Total
🟢 Statements 81.27% 18867/23214
🟡 Branches 66.93% 8214/12273
🟡 Functions 74.71% 4971/6654
🟢 Lines 81.67% 18468/22612

Test suite run success

4802 tests passing in 631 suites.

Report generated by 🧪jest coverage report action from 5c05cfb

@KrumTy KrumTy marked this pull request as ready for review July 16, 2025 14:56
],
"moduleNameMapper": {
"src/(.*)": "<rootDir>/$1",
"apiSrc/(.*)": "<rootDir>/$1",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was this unused?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we didn't use apiSrc from api folder. so it should be safe to remove

@valkirilov
Copy link
Copy Markdown
Member

Great job on this, using an OpenAPI contract to keep services in sync is a solid and widely used approach, especially when different tech stacks are involved or when the code lives in separate repos. In those situations, generating types from Swagger docs makes it easier to keep services aligned and follow the correct communication protocol.

That said, in our case, we're working in a monorepo, and both the backend and frontend use TypeScript. I don't want to sound too negative, but adding a (semi-automatic) code generation tool here feels like extra complexity and might hide the real issue that we don't have a single source of truth for our types.

Instead of adding another layer, I think it would be more effective to focus on fixing the type mismatches directly. We could do this by adjusting how the frontend uses the types, or better yet, by introducing a shared types package (or something similar) so both sides rely on the same definitions.

Another thing to consider is that OpenAPI doesn't always match perfectly with the actual TypeScript code, so I think it's better to stick to the code interfaces as the main source of truth.

That said, if there are plans to move away from the monorepo and split the services, then generating types from Swagger would absolutely make sense, and it will probably be the best approach. I'm not against it overall, just think we should focus on solving the core issue first.

So, I'll be happy to chat more about this so we can agree on which is the best way to reduce these TypeScript errors and resolve the type mismatches between the BE and the FE.

@pd-redis
Copy link
Copy Markdown
Contributor

Great job on this, using an OpenAPI contract to keep services in sync is a solid and widely used approach, especially when different tech stacks are involved or when the code lives in separate repos. In those situations, generating types from Swagger docs makes it easier to keep services aligned and follow the correct communication protocol.

That said, in our case, we're working in a monorepo, and both the backend and frontend use TypeScript. I don't want to sound too negative, but adding a (semi-automatic) code generation tool here feels like extra complexity and might hide the real issue that we don't have a single source of truth for our types.

Instead of adding another layer, I think it would be more effective to focus on fixing the type mismatches directly. We could do this by adjusting how the frontend uses the types, or better yet, by introducing a shared types package (or something similar) so both sides rely on the same definitions.

Another thing to consider is that OpenAPI doesn't always match perfectly with the actual TypeScript code, so I think it's better to stick to the code interfaces as the main source of truth.

That said, if there are plans to move away from the monorepo and split the services, then generating types from Swagger would absolutely make sense, and it will probably be the best approach. I'm not against it overall, just think we should focus on solving the core issue first.

So, I'll be happy to chat more about this so we can agree on which is the best way to reduce these TypeScript errors and resolve the type mismatches between the BE and the FE.

I don't think BE types should be used at all on FE, besides in the apiService.
Many type problems are caused because RedisString can be either string or RedisStringBuffer, and its being used as a string in most places, but in a few it is converted to string or to buffer. However, it is not actually a Buffer, but a data structure with type and data properties - so in this example, I don't think it should be typed as Buffer (it is send like one from BE, but serialized to JSON as plain object), but as:

{
type: string
data: number[]
}

Since we're passing via the API, using the same types is a lie for anything that is not supported by JSON and converting from json to buffer for example is a waste since we don't use it as buffer anywhere

@pawelangelow
Copy link
Copy Markdown
Contributor

What are we going to do here?

@KIvanow
Copy link
Copy Markdown
Contributor

KIvanow commented Jul 30, 2025

What are we going to do here?

Were the comments from @pd-redis and @valkirilov adressed @KrumTy ?

Before all of the conflicts, were the tests (despite being flaky) passing?

Is anyone against these changes (@ArtemHoruzhenko and @dantovska as only ones not tagged so far)?

Copy link
Copy Markdown
Contributor

@ArtemHoruzhenko ArtemHoruzhenko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like a lot of dto's fields were missed. Well done!

],
"moduleNameMapper": {
"src/(.*)": "<rootDir>/$1",
"apiSrc/(.*)": "<rootDir>/$1",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we didn't use apiSrc from api folder. so it should be safe to remove

Comment on lines +3 to +20
export const REDIS_STRING_SCHEMA = {
type: String,
oneOf: [
{ type: 'string' },
{
type: 'object',
properties: {
type: { type: 'string', enum: ['Buffer'], example: 'Buffer' },
data: {
type: 'array',
items: { type: 'number' },
example: [61, 101, 49],
},
},
required: ['type', 'data'],
},
],
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! I assume before we had "string" in swagger, right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was mostly string yes

Comment on lines +22 to +26
export const ApiRedisString = (
description: string = undefined,
isArray = false,
required = true,
) =>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like it will be better to join isArray and required and send as a second argument {isArray: string, required: boolean}. It will be more flexible/scalable approach.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I figured I'll get that comment
it was just 2 params until the very end when a 3rd was needed for a few service methods

type: String,
isArray: true,
})
@ApiRedisString('Hash fields', true)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and here it will be clear what we are doing: e.g. @ApiRedisString('Hash fields', { isArray: true })

Comment on lines +21 to 25
@ApiProperty({
description: 'Cloud API key',
type: String,
})
capiKey?: string; // api_access_key
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fields under 'secure' group are not sent to UI not sure we must include them into swagger

Comment on lines +18 to +21
// ref: /api/src/common/constants/user.ts
const DEFAULT_USER_ID = '1'
const DEFAULT_SESSION_ID = '1'
// ref: /api/src/modules/cloud/auth/exceptions/cloud-oauth.unexpected-error.exception.ts
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is ref here? do we have circular deps here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is where the class/constant was ported over since it can't be shipped via swagger
(rare places required this)

/* eslint-disable */
/**
* Redis Insight Backend API
* Redis Insight Backend API
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

twice for those who didn't get 😄

Comment on lines +21 to +24
/**
*
* @export
*/
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

those are all autogenerated helpers (everything inside api-client/)
common.ts is imported in api.ts

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a big fan of having millions of .md files in our source code. Why we need it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

they come by default via the auto generator cli. I suppose there will be a config to omit markdown

@KIvanow KIvanow deleted the branch fe/feature/RI-7039-replace-eui August 7, 2025 12:25
@KIvanow KIvanow closed this Aug 7, 2025
KrumTy added a commit that referenced this pull request May 8, 2026
…ript to autogenerate types for clients (#5863)

* feat(api): scaffold OpenAPI client generation pipeline

Add tooling to auto-generate a typescript-axios client from the
NestJS Swagger spec on yarn install. The spec and the generated
client are treated as ephemeral build artifacts (gitignored), so
the BE Swagger decorators remain the single source of truth.

- Pin openapi-generator-cli to 7.14.0 via openapitools.json
- Add dump-openapi.ts to extract the spec without an HTTP listener
- Wire postinstall hook to regenerate the client on every install
- Port ApiRedisString decorator for Buffer/RedisString wire format
- Lint guard against new apiSrc/* and src/* imports from the UI

References: #RI-7682

* feat(api): enable strict OpenAPI spec validation

Fix 7 schema/path issues so the generator runs without
--skip-validate-spec:

- rdi-statistics: register union members (Table/Blocks/Info sections)
  as top-level schemas via @ApiExtraModels + getSchemaPath
- vector-set DTOs: drop redundant isArray on number[] fields that
  produced nested arrays in the spec
- bulk-actions and database-analysis controllers: declare the
  {dbInstance} path parameter via @ApiRedisParams

Refs RI-7682

* feat(api): decorate Notification model for OpenAPI

Add @ApiProperty/@ApiPropertyOptional to all fields on the
Notification class so the generated client surfaces a real
Notification interface and types NotificationsDto.notifications
as Array<Notification> (was Array<object>).

Refs RI-7682

* fix(api): correct database controller response types

Two response-type bugs caused the generated client to expose the
wrong shapes for two database endpoints:

- bulkDeleteDatabaseInstance declared DeleteDatabasesDto (the
  request body shape) but actually returns DeleteDatabasesResponse.
- exportConnections returns ExportDatabase[] but the swagger
  metadata was missing isArray: true, so it appeared as a single
  object in the spec.

Fix the @apiendpoint responses metadata to match the actual return
types. The generated client now exposes
AxiosPromise<DeleteDatabasesResponse> and
AxiosPromise<Array<ExportDatabase>>.

Refs RI-7682

* feat(api): decorate browser/keys for OpenAPI codegen

Adopt @ApiRedisString for RedisString/Buffer fields and add response-type
decorators on keys.controller endpoints so the generated TS client emits
proper interfaces and array-typed responses.

Refs RI-7682

* feat(api): decorate browser/hash for OpenAPI codegen

Refs RI-7682

* feat(api): decorate browser/list for OpenAPI codegen

Refs RI-7682

* feat(api): decorate browser/set for OpenAPI codegen

Refs RI-7682

* feat(api): decorate browser/z-set for OpenAPI codegen

Refs RI-7682

* feat(api): decorate browser/string for OpenAPI codegen

Refs RI-7682

* feat(api): decorate browser/stream for OpenAPI codegen

Refs RI-7682

* feat(api): decorate browser/redisearch for OpenAPI codegen

Refs RI-7682

* feat(api): decorate browser/rejson-rl for OpenAPI codegen

Refs RI-7682

* feat(api): decorate cloud module for OpenAPI codegen

Refs RI-7682

* feat(api): decorate pub-sub for OpenAPI codegen

Refs RI-7682

* feat(api): decorate cluster-monitor models for OpenAPI codegen

Refs RI-7682

* feat(api): decorate database-recommendation for OpenAPI codegen

Refs RI-7682

* feat(api): decorate database model for OpenAPI codegen

Refs RI-7682

* feat(api): decorate slow-log controller for OpenAPI codegen

Refs RI-7682

* feat(api): decorate custom-tutorial controller for OpenAPI codegen

Refs RI-7682

* feat(api): decorate database-analysis for OpenAPI codegen

Apply targeted improvements from PR #4725:
- list endpoint: correct description and array response type
- update endpoint: correct description (analysis vs instance)
- get/update endpoints: declare :id path param
- models: mark NspSummary arrays with isArray, switch Key.name and
  NspSummary.nsp to ApiRedisString to model the wire format

dbInstance path param remains declared via the class-level
ApiRedisParams() introduced in the strict-validation commit.

Refs RI-7682

* style(api): clean up lint after OpenAPI decoration

- drop unused ApiPropertyOptional/ApiProperty imports left over after
  switching to ApiRedisString
- prettier formatting in cloud-auth-request-info, cloud-auth-response,
  and rdi-statistics

Refs RI-7682

* relocate auto generated client to ./redisinsight/api-client

* fix(ci): unblock integration test Docker build after postinstall change

* refactor(api): swap OpenAPI generator to @hey-api/openapi-ts

* fix(api): restore cloud-auth controller path

* refactor(api): swap OpenAPI generator to @hey-api/openapi-ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants