docs(api): add docs to initialize an API project

This commit is contained in:
hantsy 2020-06-13 12:45:34 +08:00 committed by Hantsy Bai
parent 8308122de9
commit 191cc9b73d
4 changed files with 783 additions and 23 deletions

35
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,35 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"name": "vscode-jest-tests",
"request": "launch",
"args": [
"--runInBand"
],
"cwd": "${workspaceFolder}",
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"disableOptimisticBPs": true,
"program": "${workspaceFolder}/node_modules/jest/bin/jest"
},
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"skipFiles": [
"<node_internals>/**"
],
"program": "${workspaceFolder}\\start",
"preLaunchTask": "tsc: build - tsconfig.json",
"outFiles": [
"${workspaceFolder}/dist/**/*.js"
]
}
]
}

713
docs/curd.md Normal file
View File

@ -0,0 +1,713 @@
# Building RESTful APIs with NestJS
As described in the [Nestjs](https://nestjs.com) website, Nestjs is *a progressive Node.js framework for building efficient, reliable and scalable server-side applications.*
Nestjs combines the best programming practice and the excellent technologies from the communities.
* A lot of NestJS concepts are heavily inspired by the effort of the popular frameworks in the world, esp [Angular](https://www.angular.io), [Spring WebMVC](http://www.spring.io).
* Nestjs is flexbile, you are free to choose [Expressjs](https://expressjs.com/) or [Fastify](https://www.fastify.io) as the background engine.
* Nestjs provides a lot of third party project integration, from database operations, such as Mongoose, TypeORM, etc. to a Message Broker, such as RabbitMQ etc.
If you are new to Nestjs like me but has some experience of Angular or Spring WebMVC, kickstarting a Nestjs project is really not difficult.
## Generating a Nestjs project
Make sure you have installed the latest [Nodejs](https://nodejs.org/en/).
```bash
npm i -g @nestjs/cli
```
When it is finished, there is a `nest` command available in the `Path`. The usage of `nest` is similar with `ng` (Angular CLI), type `nest --help` in the terminal to list help for all commands.
```bash
nest --help
Usage: nest <command> [options]
Options:
-v, --version Output the current version.
-h, --help Output usage information.
Commands:
new|n [options] [name] Generate Nest application.
build [options] [app] Build Nest application.
start [options] [app] Run Nest application.
info|i Display Nest project details.
update|u [options] Update Nest dependencies.
add [options] <library> Adds support for an external library to your project.
generate|g [options] <schematic> [name] [path] Generate a Nest element.
Available schematics:
┌───────────────┬─────────────┐
│ name │ alias │
│ application │ application │
│ class │ cl │
│ configuration │ config │
│ controller │ co │
│ decorator │ d │
│ filter │ f │
│ gateway │ ga │
│ guard │ gu │
│ interceptor │ in │
│ interface │ interface │
│ middleware │ mi │
│ module │ mo │
│ pipe │ pi │
│ provider │ pr │
│ resolver │ r │
│ service │ s │
│ library │ lib │
│ sub-app │ app │
└───────────────┴─────────────┘
```
Now generate a Nestjs project via:
```bash
nest new nestjs-sample
```
Open it in your favorite IDEs, such as [Intellij WebStorm](https://www.jetbrains.com/webstorm/) or [VSCode](https://code.visualstudio.com/).
## Exploring the project files
Expand the project root, you will see the following like tree nodes.
```bash
.
├── LICENSE
├── nest-cli.json
├── package.json
├── package-lock.json
├── README.md
├── src
│   ├── app.controller.spec.ts
│   ├── app.controller.ts
│   ├── app.module.ts
│   ├── app.service.ts
│   └── main.ts
├── test
│   ├── app.e2e-spec.ts
│   └── jest-e2e.json
├── tsconfig.build.json
└── tsconfig.json
```
The default structure of this project is very similar with the one generated by Angular CLI.
* *src/main.ts* is the entry file of this application.
* *src/app\** is the top level component in a nest application.
* There is an a*pp.module.ts* is a Nestjs `Module` which is similar with Angular `NgModule`, and used to organize codes in logic view.
* The *app.service.ts* is an `@Injectable` component, similar with the service in Angular or Spring's Service, it is used for handling business logic. A service is annotated with `@Injectable`.
* The *app.controller.ts* is the controller of MVC, to handle incoming request, and responds the handled result back to client.
* The *app.controller.spec.ts* is test file for *app.controller.ts*. Nestjs uses [Jest](https://jestjs.io/) as testing framework.
* *test* folder is for storing e2e test files.
## Defining the APIs
I will reuse the concept I've used in the former examples - the blogging posts.
* GET /posts - Get all posts
* GET /posts/id - Get post by id
* POST /posts - Save a post
* PUT /posts/id - Update the certain post specified by id
* DELETE /posts/id - Delete a post
In the next steps, we will create:
* A new module `post` to organize all the features.
* A `Post` interface to present the `Post` entity resource.
* A `PostService` component to serve the data for controller. In this post, we use dummy data storage temporarily, and replace it with a real Mongo in the future post.
* A `PostController` to expose APIs which is responsible for handling incoming requests.
## Creating PostService
Following the Nestjs coding style, first of all, let's generate a module named `post`:
```bash
nest g mo post
```
`PosModule` is imported into the top-level `AppModule`. Check the content of file *src/app/app.module.ts*:
```typescript
//... other imports
import { PostModule } from './post/post.module';
@Module({
imports: [PostModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
```
Then generate the `PostService`, `Post` respectively.
```bash
nest g s post
nest g interface post
```
The `PostService` is added to `PostModule` automatcially when it is generated.
```typescript
//...other imports
import { PostService } from './post.service';
@Module({
providers: [PostService]
})
export class PostModule {}
```
After it is done there are 4 files created in the *src/app/post* folder, including two specs files for testing `PostController` and `PostService`.
```bash
post.interface.ts
post.module.ts
post.service.spec.ts
post.service.ts
```
Firstly, let's open `post.interface.ts` to model the `Post` entity.
```typescript
export interface Post {
id?: number;
title:string;
content: string;
createdAt?: Date,
updatedAt?: Date
}
```
In the above interface, add some fields as you see, here we make the `id`, `createdAt`, `updatedAt` are optional now.
Let's create a method in `PostService` to fetch all posts.
```typescript
@Injectable()
export class PostService {
private posts: Post[] = [
{
id: 1,
title: 'Generate a NestJS project',
content: 'content',
createdAt: new Date(),
},
{
id: 2,
title: 'Create CRUD RESTful APIs',
content: 'content',
createdAt: new Date(),
},
{
id: 3,
title: 'Connect to MongoDB',
content: 'content',
createdAt: new Date(),
},
];
findAll(): Observable<Post> {
return from(this.posts);
}
}
```
In the codes, we used an array `posts` for the background data storage. The `findAll` method returns a `Observerable` which is created from the dummy `posts` we have declared.
Add a test for this method. Open `post.service.spec.ts`, add a new test case.
```typescript
describe('PostService', () => {
let service: PostService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [PostService],
}).compile();
service = module.get<PostService>(PostService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it('getAllPosts should return 3 posts', done => {
service
.findAll()
.pipe(take(3), toArray())
.subscribe({
next: data => expect(data.length).toBe(3),
error: error => console.log(error),
complete: done(),
});
});
}
```
In the `beforeEach` , it prepares a `TestingModule` to assemble resources in the test.
> Like the annotations (@BeforeEach, etc.) provided JUnit 5 in Java world, there are some similar hooks ready for preparing the test and doing clean work in a Jest test, such as `beforeAll, afterAll, beforeEache, afterEach` , etc.
In the newly added `it('getAllPosts should return 3 posts', done => {}` , it uses a `done` handler to mark the an asynchronous task is done. It is very helpful to test `Promise` or `Observerable` invocation in a test.
The `pipe` accepts a series of Rxjs operators for some `processor` working, such as filtering, transforming, collecting, etc. check the [Rxjs official docs](https://rxjs.dev/operator-decision-tree).
The `subscribe` accept a subscriber to handle the data stream, see the [Subscriber API](https://rxjs.dev/api/index/class/Subscriber) for more details.
Run `npm run test` command in the root folder to run all tests.
```bash
npm run test
> nestjs-sample@0.0.1 test D:\hantsylabs\nestjs-sample
> jest
...
Time: 4.563 s, estimated 11 s
Ran all test suites.
```
> If you want to track the changes on test codes and rerun the test cases automatically, use `npm run test:watch` instead.
It works.
Let's modify the `findAll` slightly, make it accept a keyword to query the posts.
```typescript
@Injectable()
export class PostService {
//...
findAll(keyword?: string): Observable<Post> {
if (keyword) {
return from(this.posts.filter(post => post.title.indexOf(keyword) >= 0));
}
return from(this.posts);
}
}
```
Add another test to verify filtering the posts by keyword.
```typescript
it('getAllPosts with keyword should return 1 post', done => {
service
.findAll('Generate')
.pipe(take(3), toArray())
.subscribe({
next: data => expect(data.length).toBe(1),
error: error => console.log(error),
complete: done(),
});
});
```
When using a keyword *Generate*, only one item will be included in the data stream. Run the test again, it will pass.
Let's move the next one. Create a `findById` method in `PostService` class. When a post is found return the found post else return a Rxjs `EMPTY`.
```typescript
findById(id: number): Observable<Post> {
const found = this.posts.find(post => post.id === id);
if (found) {
return of(found);
}
return EMPTY;
}
```
And add a test for the case when a post is found.
```typescript
it('getPostById with existing id should return 1 post', done => {
service.findById(1).subscribe({
next: data => {
expect(data.id).toBe(1);
expect(data.title).toEqual('Generate a NestJS project');
},
error: error => console.log(error),
complete: done(),
});
});
```
When the `findById` return a `EMPTY`, I tried to use the same approach to verify the result in the `next` handler, but failed, the `next` is never called, I started a [topic on stackoverflow](https://stackoverflow.com/questions/62208107/how-test-a-rxjs-empty-operater-using-jest), and got two solutions for handling the `EMPTY` stream.
The first solution is utilizing the *testing* facility from Rxjs. Set up a `TestScheduler` for test, and use `expectObservable` to assert the data stream in [marble diagrams](https://rxjs-dev.firebaseapp.com/guide/testing/internal-marble-tests).
```typescript
beforeEach(() => {
testScheduler = new TestScheduler((actual, expected) => {
// asserting the two objects are equal
expect(actual).toEqual(expected);
});
});
// This test will actually run *synchronously*
it('test complete for empty()', () => {
testScheduler.run(helpers => {
const { cold, expectObservable, expectSubscriptions } = helpers;
expectObservable(service.findById(10001)).toBe('|');
});
});
```
The second test is using a signal myself to make sure it is completed.
```typescript
it('getPostById with none existing id should return empty', done => {
let called = false;
service.findById(10001).subscribe({
next: data => {
console.log(data);
called = true;
},
error: error => {
console.log(error);
called = true;
},
complete: () => {
expect(called).toBeFalsy();
done();
},
});
```
Another workaround is converting the `EMPTY` stream to an array and asserting it is an empty array in the `next` handler.
```typescript
it('getPostById with none existing id should return empty', done => {
service
.findById(10001)
.pipe(toArray())
.subscribe({
next: data => expect(data.length).toBe(0),
error: error => console.log(error),
complete: done(),
});
});
```
Ok, we have resolved the `emtpy` stream testing issue. Go ahead.
Next, let's create a method to save a new `Post` into the data storage. Since we are using an array for the dummy storage, it is simple when saving a new `Post`, just append it into the end of the existing `posts`:
```typescript
save(data: Post): Observable<Post> {
const post = { ...data, id: this.posts.length + 1, createdAt: new Date() };
this.posts = [...this.posts, post];
return from(this.posts);
}
```
> Here, we return the new array as result. In a real world application, most of case it is better to return the new persisted object or the id of the new post.
Create a test for saving a new post, verify if the length of the array is increased and if the `createdAt` is set in the new post.
```typescript
it('save should increase the length of posts', done => {
service
.save({
id: 4,
title: 'test title',
content: 'test content',
})
.pipe(toArray())
.subscribe({
next: data => {
expect(data.length).toBe(4);
expect(data[3].createdAt).toBeTruthy();
},
error: error => console.log(error),
complete: done(),
});
});
```
Execute `npm run test` to make make sure it works.
Similarly, create a `update` method in `PostService` class to update the existing post.
```typescript
update(id: number, data: Post): Observable<Post> {
this.posts = this.posts.map(post => {
if (id === post.id) {
post.title = data.title;
post.content = data.content;
post.updatedAt = new Date();
}
return post;
});
return from(this.posts);
}
```
Create a test for `update` method. In the test, we updated the first item in the `posts` data store, and update it, and finally verify the changes.
```typescript
it('update should change the content of post', done => {
service
.update(1, {
id: 1,
title: 'test title',
content: 'test content',
createdAt: new Date(),
})
.pipe(take(4), toArray())
.subscribe({
next: data => {
expect(data.length).toBe(3);
expect(data[0].title).toBe('test title');
expect(data[0].content).toBe('test content');
expect(data[0].updatedAt).not.toBeNull();
},
error: error => console.log(error),
complete: done(),
});
});
```
Create a method to delete post by id.
```typescript
deleteById(id: number): Observable<boolean> {
const idx: number = this.posts.findIndex(post => post.id === id);
if (idx >= 0) {
this.posts = [
...this.posts.slice(0, idx),
...this.posts.slice(idx + 1),
];
return of(true);
}
return of(false);
}
```
Create a test case in `post.service.test` to verify the functionality of the `deleteById` method.
```typescript
it('deleteById with existing id should return true', done => {
service.deleteById(1).subscribe({
next: data => expect(data).toBeTruthy,
error: error => console.log(error),
complete: done(),
});
});
it('deleteById with none existing id should return false', done => {
service.deleteById(10001).subscribe({
next: data => expect(data).toBeFalsy,
error: error => console.log(error),
complete: done(),
});
});
```
OK, all methods used for CRUD functionalities are ready, let's move to the controller.
## Creating PostController
Like Spring WebMVC, in the NestJS world, the controller is responsible for handling incoming requests from the client, and sending back the handled result to the client.
Generate a controller using `nest` command:
```bash
nest g co post
```
It will add two files into the *src/app/post* folder.
```bash
post.controller.spec.ts
post.controller.ts
```
And `PostController` is registered in `PostModule` automatcially when it is generated.
```typescript
//...other imports
import { PostController } from './post.controller';
@Module({
controllers: [PostController],
providers: [PostService]
})
export class PostModule {}
```
Open the *post.controller.ts* file and enrich the `PostController` class as we planned.
```typescript
@Controller('posts')
export class PostController {
constructor(private postService: PostService) {}
@Get('')
getAllPosts(@Query('q') keyword?: string): Observable<BlogPost[]> {
return this.postService.findAll(keyword).pipe(take(10), toArray());
}
@Get(':id')
getPostById(@Param('id', ParseIntPipe) id: number): Observable<BlogPost> {
return this.postService.findById(id);
}
@Post('')
createPost(@Body() post: BlogPost):Observable<BlogPost[]> {
return this.postService.save(post).pipe(toArray());
}
@Put(':id')
updatePost(@Param('id', ParseIntPipe) id: number, @Body() post: BlogPost): Observable<BlogPost[]> {
return this.postService.update(id, post).pipe(toArray());
}
@Delete(':id')
deletePostById(@Param('id', ParseIntPipe) id: number): Observable<boolean> {
return this.postService.deleteById(id);
}
}
```
In the above codes:
* In the `constructor`, we add `PostService` as its arguments, the `PostService` component will be injected automatically.
* The `Controller` annotation indicates it is a controller, and it uses `/posts` as base uri all methods.
* The `Get`, `Post`, `Put`, and `Delete` on methods are used for handling different HTTP methods.
* The `@Param` binds the path parameters (`:id`) to the method arguments, and there is `ParseIntPipe` applied in the second arguments of Param. Pipe is a Angular concept, here it is more close to Spring's converters and validators.
* `@Body` parses the request body into a `Post` object.
> In Typescript language, we call `@Controller` syntax as Decorator. For those who are familiar with Spring WebMVC, the word annotation is easier to understand.
Now we add tests for `PostController`.
Open *post.controller.spec.ts* , add the following tests.
```typescript
describe('Post Controller', () => {
let controller: PostController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [PostService],
controllers: [PostController],
}).compile();
controller = module.get<PostController>(PostController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
it('GET on /posts should return all posts', async () => {
const posts = await controller.getAllPosts().toPromise();
expect(posts.length).toBe(3);
});
it('GET on /posts/1 should return one post ', done => {
controller.getPostById(1).subscribe(data => {
expect(data.id).toEqual(1);
done();
});
});
it('POST on /posts should return all posts', async () => {
const post:Post = {id:4, title:'test title', content:'test content'};
const posts = await controller.createPost(post).toPromise();
expect(posts.length).toBe(4);
});
it('PUT on /posts/1 should return all posts', done => {
const post:Post = {id:4, title:'test title', content:'test content'};
controller.updatePost(1, post).subscribe(data => {
expect(data.length).toBe(3);
expect(data[0].title).toEqual('test title');
expect(data[0].content).toEqual('test content');
expect(data[0].updatedAt).toBeTruthy();
done();
});
});
it('DELETE on /posts/1 should return true', done => {
controller.deletePostById(1).subscribe(data => {
expect(data).toBeTruthy();
done();
});
});
it('DELETE on /posts/1001 should return false', done => {
controller.deletePostById(1001).subscribe(data => {
expect(data).toBeFalsy();
done();
});
});
});
```
Run the tests again and make sure it works.
## Run the application
Now let's try to run the application.
Open a terminal, and go to the root folder of the project, and execute the following command.
```bash
>npm run start
> nestjs-sample@0.0.1 start D:\hantsylabs\nestjs-sample
> nest start
[Nest] 9956 - 06/13/2020, 12:04:50 PM [NestFactory] Starting Nest application...
[Nest] 9956 - 06/13/2020, 12:04:50 PM [InstanceLoader] AppModule dependencies initialized +16ms
[Nest] 9956 - 06/13/2020, 12:04:50 PM [InstanceLoader] PostModule dependencies initialized +1ms
[Nest] 9956 - 06/13/2020, 12:04:50 PM [RoutesResolver] AppController {}: +10ms
[Nest] 9956 - 06/13/2020, 12:04:50 PM [RouterExplorer] Mapped {, GET} route +4ms
[Nest] 9956 - 06/13/2020, 12:04:50 PM [RoutesResolver] PostController {/posts}: +1ms
[Nest] 9956 - 06/13/2020, 12:04:50 PM [RouterExplorer] Mapped {/posts, GET} route +1ms
[Nest] 9956 - 06/13/2020, 12:04:50 PM [RouterExplorer] Mapped {/posts/:id, GET} route +2ms
[Nest] 9956 - 06/13/2020, 12:04:50 PM [RouterExplorer] Mapped {/posts, POST} route +1ms
[Nest] 9956 - 06/13/2020, 12:04:50 PM [RouterExplorer] Mapped {/posts/:id, PUT} route +2ms
[Nest] 9956 - 06/13/2020, 12:04:50 PM [RouterExplorer] Mapped {/posts/:id, DELETE} route +2ms
[Nest] 9956 - 06/13/2020, 12:04:50 PM [NestApplication] Nest application successfully started +7ms
```
When it is started up, it serves at http://localhost:3000.
Let's test the exposed APIs via `curl`:
```bash
>curl http://localhost:3000/posts
[{"id":1,"title":"Generate a NestJS project","content":"content","createdAt":"2020-06-13T04:20:21.920Z"},{"id":2,"title":"Create CRUD RESTful APIs","content":"content","createdAt":"2020-06-13T04:20:21.920Z"},{"id":3,"title":"Connect to MongoDB","content":"content","createdAt":"2020-06-13T04:20:21.920Z"}]
>curl http://localhost:3000/posts/1
{"id":1,"title":"Generate a NestJS project","content":"content","createdAt":"2020-06-13T04:20:21.920Z"}
>curl http://localhost:3000/posts -d "{\"title\":\"new post\",\"content\":\"content of my new post\"}" -H "Content-Type:application/json" -X POST
[{"id":1,"title":"Generate a NestJS project","content":"content","createdAt":"2020-06-13T04:20:21.920Z"},{"id":2,"title":"Create CRUD RESTful APIs","content":"content","createdAt":"2020-06-13T04:20:21.920Z"},{"id":3,"title":"Connect to MongoDB","content":"content","createdAt":"2020-06-13T04:20:21.920Z"},{"title":"new post","content":"content of my new post","id":4,"createdAt":"2020-06-13T04:20:52.526Z"}]
>curl http://localhost:3000/posts/1 -d "{\"title\":\"updated post\",\"content\":\"content of my upated post\"}" -H "Content-Type:application/json" -X PUT
[{"id":1,"title":"updated post","content":"content of my upated post","createdAt":"2020-06-13T04:20:21.920Z","updatedAt":"2020-06-13T04:21:08.259Z"},{"id":2,"title":"Create CRUD RESTful APIs","content":"content","createdAt":"2020-06-13T04:20:21.920Z"},{"id":3,"title":"Connect to MongoDB","content":"content","createdAt":"2020-06-13T04:20:21.920Z"},{"title":"new post","content":"content of my new post","id":4,"createdAt":"2020-06-13T04:20:52.526Z"}]
>curl http://localhost:3000/posts/1 -X DELETE
true
>curl http://localhost:3000/posts
[{"id":2,"title":"Create CRUD RESTful APIs","content":"content","createdAt":"2020-06-13T04:20:21.920Z"},{"id":3,"title":"Connect to MongoDB","content":"content","createdAt":"2020-06-13T04:20:21.920Z"},{"title":"new post","content":"content of my new post","id":4,"createdAt":"2020-06-13T04:20:52.526Z"}]
```
In the further post, we will connect to a real database instead of the dummy data.
Grab [the source codes from my github](https://github.com/hantsy/nestjs-sample).

View File

@ -41,20 +41,20 @@ describe('PostService', () => {
});
it('getPostById with existing id should return 1 post', done => {
service
.findById(1)
.pipe(take(3), toArray())
.subscribe({
next: data => expect(data.length).toBe(1),
error: error => console.log(error),
complete: done(),
});
service.findById(1).subscribe({
next: data => {
expect(data.id).toBe(1);
expect(data.title).toEqual('Generate a NestJS project');
},
error: error => console.log(error),
complete: done(),
});
});
it('getPostById with none existing id should return empty', done => {
service
.findById(10001)
.pipe(take(3), toArray())
.pipe(toArray())
.subscribe({
next: data => expect(data.length).toBe(0),
error: error => console.log(error),
@ -109,7 +109,7 @@ describe('PostService', () => {
});
});
it('deleteById with existing id should return true', done => {
it('deleteById with none existing id should return false', done => {
service.deleteById(10001).subscribe({
next: data => expect(data).toBeFalsy,
error: error => console.log(error),
@ -154,14 +154,22 @@ describe('PostService(test for empty())', () => {
});
});
it('getPostById with none existing id should return empty', done => {
it('test complete for empty():getPostById with none existing id', done => {
let called = false;
service.findById(10001).subscribe({
next: data => {
console.log('complete:' + typeof data);
expect(data).toBeNaN();
console.log(data);
called = true;
},
error: error => {
console.log(error);
called = true;
},
complete: () => {
console.log("calling complete");
expect(called).toBeFalsy();
done();
},
error: error => console.log(error),
complete: done,
});
});
});

View File

@ -1,6 +1,6 @@
import { Injectable } from '@nestjs/common';
import { Post } from './post.interface';
import { of, from, Observable, empty } from 'rxjs';
import { of, from, Observable, empty, EMPTY } from 'rxjs';
@Injectable()
export class PostService {
@ -12,13 +12,13 @@ export class PostService {
createdAt: new Date(),
},
{
id: 1,
id: 2,
title: 'Create CRUD RESTful APIs',
content: 'content',
createdAt: new Date(),
},
{
id: 1,
id: 3,
title: 'Connect to MongoDB',
content: 'content',
createdAt: new Date(),
@ -34,11 +34,11 @@ export class PostService {
}
findById(id: number): Observable<Post> {
const found = this.posts.filter(post => post.id === id);
if (found.length > 0) {
return of(found[0]);
const found = this.posts.find(post => post.id === id);
if (found) {
return of(found);
}
return empty();
return EMPTY;
}
save(data: Post): Observable<Post> {
@ -62,7 +62,11 @@ export class PostService {
deleteById(id: number): Observable<boolean> {
const idx: number = this.posts.findIndex(post => post.id === id);
if (idx >= 0) {
this.posts.splice(idx);
// this.posts.splice(idx, 1);
this.posts = [
...this.posts.slice(0, idx),
...this.posts.slice(idx + 1),
];
return of(true);
}
return of(false);