# SPFx SharePoint Development: Quickly Generate List-Based Models Using a Single Command

In a recent post, [I shared my CLI](https://spfx-app.dev/my-cli-for-spfx-development) `@spfxappdev/cli` which has some awesome features. Many of these features come from my other posts, like "[My personal tips how to configure a SPFx project after creation](https://spfx-app.dev/my-personal-tips-how-to-configure-a-spfx-project-after-creation)"

[Today I published a new version of the CLI, v1.1.2.](https://www.npmjs.com/package/@spfxappdev/cli) This version comes with a new feature. It will help you to save a lot of time.

I guess almost everyone who works with SharePoint and SharePoint API has the same challenge. You need to aggregate a list and the items in it. So far so good, but sometimes the (internal) field names are, shall we say, "unfriendly". For example, if you create a new field via the UI and choose a display name like `My Fancy Column`, SharePoint generates the internal/static name and sets it to `My_x0020_Fancy_x0020_Column`  

To get the value from the list item (via SharePoint REST API), you need to do something like this:

```typescript
const myModel = new MyModel();
const listItem = listItemCollection[i];
myModel.myFancyColumn = listItem['My_x0020_Fancy_x0020_Column'];
```

And that's exactly what can be done faster and easier with the CLI in just a few seconds. Let's demonstrate it using the standard `SitePages` library.

```bash
#Long
spfxappdev generate model Page --weburl https://{tenant}.sharepoint.com --username "your@email.com" --password "yourPW" --list SitePages
#Short (with alias)
spfx g m Page --u https://{tenant}.sharepoint.com --user "your@email.com" --p "yourPW" --l SitePages
```

The generated interface is:

```typescript
export interface IPage {
    name: string;
	complianceAssetId: string;
	wikiContent: string;
	title: string;
	authoringCanvasContent: any;
	bannerImageURL: UrlFieldValue;
	description: string;
	promotedState: number;
	firstPublishedDate: Date;
	pageLayoutContent: any;
	authorBylineId: number[];
	topicHeader: string;
	sitePageFlags: string[];
	callToAction: string;
	originalSourceUrl: string;
	originalSourceSiteID: string;
	originalSourceWebID: string;
	originalSourceListID: string;
	originalSourceItemID: string;
	id: number;
	contentType: string;
	created: Date;
	createdById: number;
	modified: Date;
	modifiedById: number;
	copySource: string;
	checkedOutToId: number;
	checkInCommentId: number;
	type: string;
	fileSize: string;
	itemChildCountId: number;
	folderChildCountId: number;
	commentCountId: number;
	likeCountId: number;
	sensitivityId: number;
	edit: string;
	sourceVersionConvertedDocumentId: number;
	sourceNameConvertedDocumentId: number;
}
```

The generated class looks like this:

```typescript
import { IPage } from './';
import { mapper } from '@spfxappdev/mapper';
import { UrlFieldValue } from './';
export class Page implements IPage {
    @mapper({ nameOrPath: 'FileLeafRef',  })
	public name: string;

	@mapper({ nameOrPath: 'ComplianceAssetId', toClassOnly: true })
	public complianceAssetId: string;

	@mapper({ nameOrPath: 'WikiField',  })
	public wikiContent: string;

	@mapper({ nameOrPath: 'Title',  })
	public title: string;

	@mapper({ nameOrPath: 'CanvasContent1',  })
	public authoringCanvasContent: any;

	@mapper({ nameOrPath: 'BannerImageUrl', type: UrlFieldValue,  })
	public bannerImageURL: UrlFieldValue;

	@mapper({ nameOrPath: 'Description', toClassOnly: true })
	public description: string;

	@mapper({ nameOrPath: 'PromotedState', toClassOnly: true })
	public promotedState: number;

	@mapper({ nameOrPath: 'FirstPublishedDate', type: Date, toClassOnly: true })
	public firstPublishedDate: Date;

	@mapper({ nameOrPath: 'LayoutWebpartsContent',  })
	public pageLayoutContent: any;

	@mapper({ nameOrPath: 'OData__AuthorBylineId.results',  })
	public authorBylineId: number[];

	@mapper({ nameOrPath: 'OData__TopicHeader',  })
	public topicHeader: string;

	@mapper({ nameOrPath: 'OData__SPSitePageFlags.results', toClassOnly: true })
	public sitePageFlags: string[];

	@mapper({ nameOrPath: 'OData__SPCallToAction',  })
	public callToAction: string;

	@mapper({ nameOrPath: 'OData__OriginalSourceUrl', toClassOnly: true })
	public originalSourceUrl: string;

	@mapper({ nameOrPath: 'OData__OriginalSourceSiteId', toClassOnly: true })
	public originalSourceSiteID: string;

	@mapper({ nameOrPath: 'OData__OriginalSourceWebId', toClassOnly: true })
	public originalSourceWebID: string;

	@mapper({ nameOrPath: 'OData__OriginalSourceListId', toClassOnly: true })
	public originalSourceListID: string;

	@mapper({ nameOrPath: 'OData__OriginalSourceItemId', toClassOnly: true })
	public originalSourceItemID: string;

	@mapper({ nameOrPath: 'ID', toClassOnly: true })
	public id: number;

	@mapper({ nameOrPath: 'ContentType',  })
	public contentType: string;

	@mapper({ nameOrPath: 'Created', type: Date, toClassOnly: true })
	public created: Date;

	@mapper({ nameOrPath: 'AuthorId', toClassOnly: true })
	public createdById: number;

	@mapper({ nameOrPath: 'Modified', type: Date, toClassOnly: true })
	public modified: Date;

	@mapper({ nameOrPath: 'EditorId', toClassOnly: true })
	public modifiedById: number;

	@mapper({ nameOrPath: 'OData__CopySource', toClassOnly: true })
	public copySource: string;

	@mapper({ nameOrPath: 'CheckoutUserId', toClassOnly: true })
	public checkedOutToId: number;

	@mapper({ nameOrPath: 'OData__CheckinCommentId', toClassOnly: true })
	public checkInCommentId: number;

	@mapper({ nameOrPath: 'DocIcon', toClassOnly: true })
	public type: string;

	@mapper({ nameOrPath: 'FileSizeDisplay', toClassOnly: true })
	public fileSize: string;

	@mapper({ nameOrPath: 'ItemChildCountId', toClassOnly: true })
	public itemChildCountId: number;

	@mapper({ nameOrPath: 'FolderChildCountId', toClassOnly: true })
	public folderChildCountId: number;

	@mapper({ nameOrPath: 'OData__CommentCountId', toClassOnly: true })
	public commentCountId: number;

	@mapper({ nameOrPath: 'OData__LikeCountId', toClassOnly: true })
	public likeCountId: number;

	@mapper({ nameOrPath: 'OData__DisplayNameId', toClassOnly: true })
	public sensitivityId: number;

	@mapper({ nameOrPath: 'Edit', toClassOnly: true })
	public edit: string;

	@mapper({ nameOrPath: 'ParentVersionStringId', toClassOnly: true })
	public sourceVersionConvertedDocumentId: number;

	@mapper({ nameOrPath: 'ParentLeafNameId', toClassOnly: true })
	public sourceNameConvertedDocumentId: number;	
}
```

As you can see, the CLI generates the class using the `@mapper` decorator. This decorator is a part of my [npm package](https://www.npmjs.com/package/@spfxappdev/mapper). It is developed to map a model to a SharePoint API result and vice versa. Another point you may have noticed is that the internal names are stored in the `nameOrPath` property, but the model property is more the general name specification in `camelCase`.

But how you can "convert" the API Result to your model and back to the SP List Model? It is very easy:

```typescript
import { toClass, toPlain  } from '@spfxappdev/mapper';
import { Page } from '@src/models';

class MyService {

    public async getPages(): Promise<IPage[]> {
        //const pageCollection = await ...Your API call
        const pages: Page[] = toClass(Page, pageCollection);
        return pages;
    }

    public updatePage(page: IPage): Promise<void> {
        const spData = toPlain(page);

        //YOUR REST API CALL TO UPDATE THE PAGE
    }
}
```

The `toPlain` method ignores properties labeled as `toClassOnly: true` because they are read-only fields, like Editor/Author, and can't be updated.

That is great, isn't it?

By the way, you don't have to pass all the values like `--weburl`, `--username` and `--password` every time. You can set the values once via the `config set` command and then these values will be used (the password is encrypted).

Also new in this version: You can now [create local configuration files](https://github.com/SPFxAppDev/spfxcli#spfxappdev-config-create). So you can have different configurations per project.

What do you think? How do you like it? I would be happy about feedback.

Happy coding ;)
